refactor and fixes

pull/105/head
Alex Turchyn 2 years ago
parent 3b745a650d
commit 47c57158f4

@ -7,20 +7,7 @@ module Api
def create def create
submitter = Submitter.find_by!(slug: params[:submitter_slug]) submitter = Submitter.find_by!(slug: params[:submitter_slug])
blob = attachment = Submitters.create_attachment!(submitter, params)
if (file = params[:file])
ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
content_type: file.content_type)
else
ActiveStorage::Blob.find_signed(params[:blob_signed_id])
end
attachment = ActiveStorage::Attachment.create!(
blob:,
name: params[:name],
record: submitter
)
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type]) render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])
end end

@ -9,7 +9,8 @@ module Api
end end
def show def show
render json: @template.as_json(include: { author: { only: %i[id email first_name last_name] } }) render json: @template.as_json(include: { author: { only: %i[id email first_name last_name] },
documents: { only: %i[id filename uuid], methods: %i[url] } })
end end
def update def update

@ -18,23 +18,7 @@ class SubmitFormController < ApplicationController
def update def update
submitter = Submitter.find_by!(slug: params[:slug]) submitter = Submitter.find_by!(slug: params[:slug])
update_submitter!(submitter) Submitters::SubmitValues.call(submitter, params)
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
submitter.submission.save!
if submitter.completed_at?
GenerateSubmitterResultAttachmentsJob.perform_later(submitter)
if submitter.account.encrypted_configs.exists?(key: EncryptedConfig::WEBHOOK_URL_KEY)
SendWebhookRequestJob.perform_later(submitter)
end
submitter.submission.template.account.users.active.each do |user|
SubmitterMailer.completed_email(submitter, user).deliver_later!
end
end
head :ok head :ok
end end
@ -42,26 +26,4 @@ class SubmitFormController < ApplicationController
def completed def completed
@submitter = Submitter.find_by!(slug: params[:submit_form_slug]) @submitter = Submitter.find_by!(slug: params[:submit_form_slug])
end end
private
def update_submitter!(submitter)
submitter.values.merge!(normalized_values)
submitter.completed_at = Time.current if params[:completed] == 'true'
submitter.opened_at ||= Time.current
submitter.save!
submitter
end
def normalized_values
params.fetch(:values, {}).to_unsafe_h.transform_values do |v|
if params[:cast_boolean] == 'true'
v == 'true'
else
v.is_a?(Array) ? v.compact_blank : v
end
end
end
end end

@ -15,6 +15,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
canSendEmail: this.dataset.canSendEmail === 'true', canSendEmail: this.dataset.canSendEmail === 'true',
isDirectUpload: this.dataset.isDirectUpload === 'true', isDirectUpload: this.dataset.isDirectUpload === 'true',
isDemo: this.dataset.isDemo === 'true', isDemo: this.dataset.isDemo === 'true',
withConfetti: true,
values: reactive(JSON.parse(this.dataset.values)), values: reactive(JSON.parse(this.dataset.values)),
attachments: reactive(JSON.parse(this.dataset.attachments)), attachments: reactive(JSON.parse(this.dataset.attachments)),
fields: JSON.parse(this.dataset.fields) fields: JSON.parse(this.dataset.fields)

@ -12,7 +12,7 @@
:key="areaIndex" :key="areaIndex"
> >
<Teleport <Teleport
:to="`#page-${area.attachment_uuid}-${area.page}`" :to="findPageElementForArea(area)"
> >
<FieldArea <FieldArea
:ref="setAreaRef" :ref="setAreaRef"
@ -78,10 +78,16 @@ export default {
this.areaRefs = [] this.areaRefs = []
}, },
methods: { methods: {
findPageElementForArea (area) {
return (this.$root.$el?.parentNode?.getRootNode() || document).getElementById(`page-${area.attachment_uuid}-${area.page}`)
},
scrollIntoField (field) { scrollIntoField (field) {
this.areaRefs.find((area) => { this.areaRefs.find((area) => {
if (area.field === field) { if (area.field === field) {
if (document.body.style.overflow === 'hidden') { const root = this.$root.$el.parentNode.getRootNode()
const container = root.body || root.querySelector('div')
if (container.style.overflow === 'hidden') {
this.scrollInContainer(area.$el) this.scrollInContainer(area.$el)
} else { } else {
area.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' }) area.$refs.scrollToElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
@ -94,13 +100,19 @@ export default {
}) })
}, },
scrollInContainer (target) { scrollInContainer (target) {
const root = this.$root.$el.parentNode.getRootNode()
const scrollbox = root.getElementById('scrollbox')
const formContainer = root.getElementById('form_container')
const container = root.body || root.querySelector('div')
const padding = 64 const padding = 64
const boxRect = window.scrollbox.children[0].getBoundingClientRect() const boxRect = scrollbox.children[0].getBoundingClientRect()
const targetRect = target.getBoundingClientRect() const targetRect = target.getBoundingClientRect()
const targetTopRelativeToBox = targetRect.top - boxRect.top const targetTopRelativeToBox = targetRect.top - boxRect.top
window.scrollbox.scrollTop = targetTopRelativeToBox - document.body.offsetHeight + window.form_container.offsetHeight + target.offsetHeight + padding scrollbox.scrollTop = targetTopRelativeToBox - container.offsetHeight + formContainer.offsetHeight + target.offsetHeight + padding
}, },
setAreaRef (el) { setAreaRef (el) {
if (el) { if (el) {

@ -86,6 +86,7 @@ export default {
IconLogin, IconLogin,
IconDownload IconDownload
}, },
inject: ['baseUrl'],
props: { props: {
submitterSlug: { submitterSlug: {
type: String, type: String,
@ -96,6 +97,11 @@ export default {
required: false, required: false,
default: false default: false
}, },
withConfetti: {
type: Boolean,
required: false,
default: false
},
canSendEmail: { canSendEmail: {
type: Boolean, type: Boolean,
required: false, required: false,
@ -109,19 +115,21 @@ export default {
} }
}, },
async mounted () { async mounted () {
const { default: confetti } = await import('canvas-confetti') if (this.withConfetti) {
const { default: confetti } = await import('canvas-confetti')
confetti({ confetti({
particleCount: 50, particleCount: 50,
startVelocity: 30, startVelocity: 30,
spread: 140 spread: 140
}) })
}
}, },
methods: { methods: {
sendCopyToEmail () { sendCopyToEmail () {
this.isSendingCopy = true this.isSendingCopy = true
fetch(`/send_submission_email.json?submitter_slug=${this.submitterSlug}`, { fetch(this.baseUrl + `/send_submission_email.json?submitter_slug=${this.submitterSlug}`, {
method: 'POST' method: 'POST'
}).then(() => { }).then(() => {
alert('Email has been sent') alert('Email has been sent')
@ -132,7 +140,7 @@ export default {
download () { download () {
this.isDownloading = true this.isDownloading = true
fetch(`/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => { fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => {
const fileRequests = urls.map((url) => { const fileRequests = urls.map((url) => {
return () => { return () => {
return fetch(url).then(async (resp) => { return fetch(url).then(async (resp) => {

@ -55,6 +55,7 @@ export default {
IconCloudUpload, IconCloudUpload,
IconInnerShadowTop IconInnerShadowTop
}, },
inject: ['baseUrl'],
props: { props: {
message: { message: {
type: String, type: String,
@ -141,7 +142,7 @@ export default {
return await Promise.all( return await Promise.all(
blobs.map((blob) => { blobs.map((blob) => {
return fetch('/api/attachments', { return fetch(this.baseUrl + '/api/attachments', {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: 'attachments', name: 'attachments',
@ -166,7 +167,7 @@ export default {
formData.append('submitter_slug', this.submitterSlug) formData.append('submitter_slug', this.submitterSlug)
formData.append('name', 'attachments') formData.append('name', 'attachments')
return fetch('/api/attachments', { return fetch(this.baseUrl + '/api/attachments', {
method: 'POST', method: 'POST',
body: formData body: formData
}).then(resp => resp.json()).then((data) => { }).then(resp => resp.json()).then((data) => {

@ -10,7 +10,7 @@
/> />
<button <button
v-if="!isFormVisible" v-if="!isFormVisible"
class="btn btn-neutral text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base" class="btn btn-neutral flex text-white absolute rounded-none border-x-0 md:border md:rounded-full bottom-0 w-full md:mb-4 text-base"
@click.prevent="isFormVisible = true" @click.prevent="isFormVisible = true"
> >
Submit Form Submit Form
@ -194,7 +194,9 @@
class="space-y-3.5 mx-auto" class="space-y-3.5 mx-auto"
> >
<template v-if="isAnonymousChecboxes"> <template v-if="isAnonymousChecboxes">
Complete hightlighted checkboxes and click <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? 'submit' : 'next' }}</span>. <span class="text-xl">
Complete hightlighted checkboxes and click <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? 'submit' : 'next' }}</span>.
</span>
<input <input
v-for="field in currentStepFields" v-for="field in currentStepFields"
:key="field.uuid" :key="field.uuid"
@ -290,6 +292,7 @@
<FormCompleted <FormCompleted
v-else v-else
:is-demo="isDemo" :is-demo="isDemo"
:with-confetti="withConfetti"
:can-send-email="canSendEmail" :can-send-email="canSendEmail"
:submitter-slug="submitterSlug" :submitter-slug="submitterSlug"
/> />
@ -331,6 +334,11 @@ export default {
IconArrowsDiagonalMinimize2, IconArrowsDiagonalMinimize2,
FormCompleted FormCompleted
}, },
provide () {
return {
baseUrl: this.baseUrl
}
},
props: { props: {
submitterSlug: { submitterSlug: {
type: String, type: String,
@ -350,6 +358,16 @@ export default {
required: false, required: false,
default: () => [] default: () => []
}, },
withConfetti: {
type: Boolean,
required: false,
default: false
},
baseUrl: {
type: String,
required: false,
default: ''
},
fields: { fields: {
type: Array, type: Array,
required: false, required: false,
@ -357,16 +375,17 @@ export default {
}, },
authenticityToken: { authenticityToken: {
type: String, type: String,
required: true required: false,
default: ''
}, },
isDirectUpload: { isDirectUpload: {
type: Boolean, type: Boolean,
required: true, required: false,
default: false default: false
}, },
isDemo: { isDemo: {
type: Boolean, type: Boolean,
required: true, required: false,
default: false default: false
}, },
values: { values: {
@ -429,10 +448,17 @@ export default {
) )
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) { if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
document.body.style.overflow = 'hidden' this.$nextTick(() => {
const root = this.$root.$el.parentNode.getRootNode()
const scrollbox = root.getElementById('scrollbox')
const parent = root.body || root.querySelector('div')
window.scrollbox.classList.add('h-full', 'overflow-y-auto') parent.style.overflow = 'hidden'
window.scrollbox.parentNode.classList.add('h-screen', 'overflow-y-auto')
scrollbox.classList.add('h-full', 'overflow-y-auto')
scrollbox.parentNode.classList.add('h-screen', 'overflow-y-auto')
scrollbox.parentNode.style.maxHeight = '-webkit-fill-available'
})
} }
}, },
methods: { methods: {
@ -455,7 +481,7 @@ export default {
if (this.isCompleted) { if (this.isCompleted) {
return Promise.resolve({}) return Promise.resolve({})
} else { } else {
return fetch(this.submitPath, { return fetch(this.baseUrl + this.submitPath, {
method: 'POST', method: 'POST',
body: formData || new FormData(this.$refs.form) body: formData || new FormData(this.$refs.form)
}) })

@ -99,6 +99,7 @@ export default {
IconTextSize, IconTextSize,
IconArrowsDiagonalMinimize2 IconArrowsDiagonalMinimize2
}, },
inject: ['baseUrl'],
props: { props: {
field: { field: {
type: Object, type: Object,
@ -304,7 +305,7 @@ export default {
file, file,
'/direct_uploads' '/direct_uploads'
).create((_error, data) => { ).create((_error, data) => {
fetch('/api/attachments', { fetch(this.baseUrl + '/api/attachments', {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
submitter_slug: this.submitterSlug, submitter_slug: this.submitterSlug,
@ -326,12 +327,12 @@ export default {
formData.append('submitter_slug', this.submitterSlug) formData.append('submitter_slug', this.submitterSlug)
formData.append('name', 'attachments') formData.append('name', 'attachments')
return fetch('/api/attachments', { return fetch(this.baseUrl + '/api/attachments', {
method: 'POST', method: 'POST',
body: formData body: formData
}).then((resp) => resp.json()).then((attachment) => { }).then((resp) => resp.json()).then((attachment) => {
this.$emit('update:model-value', attachment.uuid)
this.$emit('attached', attachment) this.$emit('attached', attachment)
this.$emit('update:model-value', attachment.uuid)
return resolve(attachment) return resolve(attachment)
}) })

@ -33,7 +33,7 @@
<% end %> <% end %>
<% unless Docuseal.demo? %> <% unless Docuseal.demo? %>
<li> <li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, class: 'text-base hover:bg-base-300' do %> <%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
Console Console
<span class="badge badge-warning">New</span> <span class="badge badge-warning">New</span>
<% end %> <% end %>

@ -29,5 +29,7 @@ module DocuSeal
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
config.middleware.insert_before ActionDispatch::Static, ApiPathConsiderJsonMiddleware config.middleware.insert_before ActionDispatch::Static, ApiPathConsiderJsonMiddleware
ActiveSupport.run_load_hooks(:application_config, self)
end end
end end

@ -13,6 +13,20 @@ Rails.configuration.to_prepare do
response.set_header('Cache-Control', 'public, max-age=31536000') if action_name == 'show' response.set_header('Cache-Control', 'public, max-age=31536000') if action_name == 'show'
end end
ActiveStorage::Blobs::ProxyController.before_action do
response.set_header('Access-Control-Allow-Origin', '*')
response.set_header('Access-Control-Allow-Methods', 'GET')
response.set_header('Access-Control-Allow-Headers', '*')
response.set_header('Access-Control-Max-Age', '1728000')
end
ActiveStorage::Blobs::RedirectController.before_action do
response.set_header('Access-Control-Allow-Origin', '*')
response.set_header('Access-Control-Allow-Methods', 'GET')
response.set_header('Access-Control-Allow-Headers', '*')
response.set_header('Access-Control-Max-Age', '1728000')
end
ActiveStorage::DirectUploadsController.before_action do ActiveStorage::DirectUploadsController.before_action do
next if current_user next if current_user
next if Submitter.find_signed(cookies[:submitter_sid]) next if Submitter.find_signed(cookies[:submitter_sid])

@ -83,4 +83,6 @@ Rails.application.routes.draw do
end end
end end
end end
ActiveSupport.run_load_hooks(:routes, self)
end end

@ -12,7 +12,7 @@ module Submissions
end end
def create_from_emails(template:, user:, emails:, source:, send_email: false) def create_from_emails(template:, user:, emails:, source:, send_email: false)
emails = emails.to_s.scan(User::EMAIL_REGEXP) emails = emails.to_s.scan(User::EMAIL_REGEXP) unless emails.is_a?(Array)
emails.map do |email| emails.map do |email|
submission = template.submissions.new(created_by_user: user, source:) submission = template.submissions.new(created_by_user: user, source:)

@ -35,6 +35,9 @@ module Submissions
page = pdf.pages[area['page']] page = pdf.pages[area['page']]
page[:Annots] ||= []
page[:Annots] = page[:Annots].reject { |e| e[:A] && e[:A][:URI].to_s.starts_with?('file:///docuseal_field') }
width = page.box.width width = page.box.width
height = page.box.height height = page.box.height
font_size = ((page.box.width / A4_SIZE[0].to_f) * FONT_SIZE).to_i font_size = ((page.box.width / A4_SIZE[0].to_f) * FONT_SIZE).to_i
@ -66,8 +69,6 @@ module Submissions
height: image.height * scale height: image.height * scale
) )
when 'file' when 'file'
page[:Annots] ||= []
items = Array.wrap(value).each_with_object([]) do |uuid, acc| items = Array.wrap(value).each_with_object([]) do |uuid, acc|
attachment = submitter.attachments.find { |a| a.uuid == uuid } attachment = submitter.attachments.find { |a| a.uuid == uuid }

@ -11,4 +11,21 @@ module Submitters
is_more_than_two_images && original_documents.find { |a| a.uuid == attachment.uuid }&.image? is_more_than_two_images && original_documents.find { |a| a.uuid == attachment.uuid }&.image?
end end
end end
def create_attachment!(submitter, params)
blob =
if (file = params[:file])
ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
content_type: file.content_type)
else
ActiveStorage::Blob.find_signed(params[:blob_signed_id])
end
ActiveStorage::Attachment.create!(
blob:,
name: params[:name],
record: submitter
)
end
end end

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Submitters
module SubmitValues
module_function
def call(submitter, params)
update_submitter!(submitter, params)
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
submitter.submission.save!
return unless submitter.completed_at?
GenerateSubmitterResultAttachmentsJob.perform_later(submitter)
if submitter.account.encrypted_configs.exists?(key: EncryptedConfig::WEBHOOK_URL_KEY)
SendWebhookRequestJob.perform_later(submitter)
end
submitter.submission.template.account.users.active.each do |user|
SubmitterMailer.completed_email(submitter, user).deliver_later!
end
submitter
end
def update_submitter!(submitter, params)
submitter.values.merge!(normalized_values(params))
submitter.completed_at = Time.current if params[:completed] == 'true'
submitter.opened_at ||= Time.current
submitter.save!
submitter
end
def normalized_values(params)
params.fetch(:values, {}).to_unsafe_h.transform_values do |v|
if params[:cast_boolean] == 'true'
v == 'true'
else
v.is_a?(Array) ? v.compact_blank : v
end
end
end
end
end
Loading…
Cancel
Save