Compare commits

..

21 Commits

Author SHA1 Message Date
Alex Turchyn 658e8a7bd6 fix missing field areas 2023-07-27 09:59:52 +03:00
Alex Turchyn 08c3b04511 fix builder when field areas key is undefined 2023-07-26 23:47:35 +03:00
Alex Turchyn 7779eb880d adjust preview buttons style 2023-07-26 23:33:05 +03:00
Alex Turchyn 21b9ca8ea5 detach areas on remove docuemnt 2023-07-26 23:17:24 +03:00
Alex Turchyn db7e3794d9 fix auto migration 2023-07-26 22:17:13 +03:00
Alex Turchyn df1d78897a add ability to replace documents 2023-07-26 22:01:16 +03:00
Alex Turchyn e1eb2da6f9 preserve template fields per submission 2023-07-26 09:58:56 +03:00
Alex Turchyn a92d1d82c1 #23 allow smtp configure without requiring smtp user 2023-07-24 00:53:12 +03:00
Alex Turchyn 2dcfc41365 update readme 2023-07-24 00:52:21 +03:00
Alex Turchyn 15cbdfb20c fix completed page 2023-07-22 20:44:20 +03:00
Alex Turchyn a2cf8fc5b5 fix checkboxes 2023-07-22 20:20:58 +03:00
Alex Turchyn f94ef3afdc adjust self-host landing 2023-07-22 16:06:43 +03:00
Alex Turchyn 9e3a9f0eeb fix submitter form when minimized 2023-07-22 14:31:13 +03:00
Alex Turchyn 1280572e6f update readme 2023-07-22 11:34:00 +03:00
Alex Turchyn cba2e84338 add signup link into demo 2023-07-22 01:30:51 +03:00
Alex Turchyn 17ca8f013a add 'Try in Cloud' to README 2023-07-19 23:41:55 +03:00
Alex Turchyn 96ef7ffaf7 add Render deployment button to README 2023-07-19 01:40:41 +03:00
Alex Turchyn 1bbcdb8073 fix modal scroll 2023-07-19 01:38:00 +03:00
Alex Turchyn da130c3f4f fix recipients added alert 2023-07-19 01:31:27 +03:00
Alex Turchyn c15f3ecd83 update Daisyui to v3.2.1 2023-07-18 01:24:05 +03:00
Alex Turchyn df0e0e6a1b remove internal scrolling in modals 2023-07-18 01:19:29 +03:00
35 changed files with 332 additions and 101 deletions
+3
View File
@@ -61,5 +61,8 @@ RSpec/ExampleLength:
Rails/I18nLocaleTexts:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/ApplicationController:
Enabled: false
+14 -3
View File
@@ -26,6 +26,8 @@ DocuSeal is an open source platform that provides secure and efficient digital d
</p>
<h2 align="center">
<a href="https://demo.docuseal.co">✨ Live Demo</a>
<span>|</span>
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
</h2>
[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.co)
@@ -44,9 +46,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
## Deploy
| Heroku | Railway | DigitalOcean |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.app/template/IGoDnc?referralCode=ruU7JR) | [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) |
|Heroku|Railway|
|:--:|:---:|
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.app/template/IGoDnc?referralCode=ruU7JR)|
|**DigitalOcean**|**Render**|
| [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [<img alt="Deploy to Render" src="https://render.com/images/deploy-to-render-button.svg" height="40">](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
#### Docker
@@ -69,6 +73,13 @@ Run the app under a custom domain over https using docker compose (make sure you
HOST=your-domain-name.com docker-compose up
```
## For Companies
### Integrate seamless document signing into your web or mobile apps with DocuSeal!
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
[![Book an Integration Demo](https://cal.com/book-with-cal-dark.svg)](https://cal.com/docuseal)
## License
DocuSeal is released under the GNU Affero General Public License v3.0.
+4 -2
View File
@@ -22,14 +22,16 @@ class SubmissionsController < ApplicationController
create_submissions_from_submitters
end
submitters = submissions.flat_map(&:submitters)
if params[:send_email] == '1'
submissions.flat_map(&:submitters).each do |submitter|
submitters.each do |submitter|
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
end
end
redirect_to template_path(@template),
notice: "#{submissions.size} #{'recipient'.pluralize(submissions.size)} added"
notice: "#{submitters.size} #{'recipient'.pluralize(submitters.size)} added"
end
def destroy
@@ -23,6 +23,10 @@ class SubmitFormController < ApplicationController
submitter.save!
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
submitter.submission.save!
if submitter.completed_at?
GenerateSubmitterResultAttachmentsJob.perform_later(submitter)
+2 -2
View File
@@ -25,10 +25,10 @@
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap"
>
{{ field.name || fieldNames[field.type] }}
<template v-if="field.type === 'checkbox'">
<template v-if="field.type === 'checkbox' && !field.name">
{{ fieldIndex + 1 }}
</template>
<template v-else-if="!field.required">
<template v-else-if="!field.required && field.type !== 'checkbox'">
(optional)
</template>
</div>
+3 -1
View File
@@ -11,7 +11,9 @@
v-for="(area, areaIndex) in field.areas"
:key="areaIndex"
>
<Teleport :to="`#page-${area.attachment_uuid}-${area.page}`">
<Teleport
:to="`#page-${area.attachment_uuid}-${area.page}`"
>
<FieldArea
:ref="setAreaRef"
v-model="values[field.uuid]"
+12 -1
View File
@@ -51,6 +51,16 @@
Star on Github
</span>
</a>
<a
v-if="isDemo"
href="https://docuseal.co/sign_up"
class="white-button flex items-center space-x-1 w-full"
>
<IconLogin />
<span>
Create a Free Account
</span>
</a>
</div>
<div class="text-center mt-4">
Signed with
@@ -64,7 +74,7 @@
</template>
<script>
import { IconCircleCheck, IconBrandGithub, IconMail, IconDownload, IconInnerShadowTop } from '@tabler/icons-vue'
import { IconCircleCheck, IconBrandGithub, IconMail, IconDownload, IconInnerShadowTop, IconLogin } from '@tabler/icons-vue'
export default {
name: 'FormCompleted',
@@ -73,6 +83,7 @@ export default {
IconInnerShadowTop,
IconBrandGithub,
IconMail,
IconLogin,
IconDownload
},
props: {
+4 -4
View File
@@ -5,7 +5,7 @@
:values="values"
:attachments-index="attachmentsIndex"
:current-step="currentStepFields"
@focus-step="[saveStep(), goToStep($event, false, true)]"
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
/>
<button
v-if="!isFormVisible"
@@ -20,7 +20,7 @@
/>
</button>
<div
v-else
v-show="isFormVisible"
id="form_container"
class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border-base-200 border p-4 rounded"
>
@@ -213,7 +213,7 @@
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
>
<span class="text-xl">
{{ currentField.name || currentField.type + ' ' + (index + 1) }}
{{ field.name || field.type + ' ' + (index + 1) }}
</span>
</label>
</div>
@@ -430,7 +430,7 @@ export default {
this.$refs.form.querySelector('input[type="date"], input[type="text"], select')?.focus()
if (clickUpload && !this.values[this.currentField.uuid]) {
if (clickUpload && !this.values[this.currentField.uuid] && ['file', 'image'].includes(this.currentField.type)) {
this.$refs.form.querySelector('input[type="file"]')?.click()
}
})
+1 -1
View File
@@ -65,7 +65,7 @@
@blur="onNameBlur"
>{{ field.name || defaultName }}</span>
<div
v-if="isNameFocus"
v-if="isNameFocus && field.type !== 'checkbox'"
class="flex items-center ml-1.5"
>
<input
+28 -4
View File
@@ -64,8 +64,11 @@
:with-arrows="template.schema.length > 1"
:item="item"
:document="sortedDocuments[index]"
:template="template"
:is-direct-upload="isDirectUpload"
@scroll-to="scrollIntoDocument(item)"
@remove="onDocumentRemove"
@replace="onDocumentReplace"
@up="moveDocument(item, -1)"
@down="moveDocument(item, 1)"
@change="save"
@@ -133,7 +136,7 @@
<p>
<button
class="base-button"
@click="drawField = false"
@click="drawField = null"
>
Cancel
</button>
@@ -314,7 +317,7 @@ export default {
const field = {
name: '',
uuid: v4(),
required: true,
required: type !== 'checkbox',
type,
submitter_uuid: this.selectedSubmitter.uuid,
areas: [area]
@@ -333,7 +336,7 @@ export default {
type: this.dragFieldType,
uuid: v4(),
submitter_uuid: this.selectedSubmitter.uuid,
required: true
required: this.dragFieldType !== 'checkbox'
}
if (['select', 'multiple', 'radio'].includes(this.dragFieldType)) {
@@ -353,7 +356,7 @@ export default {
if (this.selectedField?.type === this.dragFieldType) {
baseArea = this.selectedAreaRef.value
} else if (previousField?.areas) {
} else if (previousField?.areas?.length) {
baseArea = previousField.areas[previousField.areas.length - 1]
} else {
if (['checkbox'].includes(this.dragFieldType)) {
@@ -417,6 +420,27 @@ export default {
this.template.schema.splice(this.template.schema.indexOf(item), 1)
}
this.template.fields.forEach((field) => {
[...(field.areas || [])].forEach((area) => {
if (area.attachment_uuid === item.attachment_uuid) {
field.areas.splice(field.areas.indexOf(area), 1)
}
})
})
this.save()
},
onDocumentReplace ({ replaceSchemaItem, schema, documents }) {
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
this.template.documents.push(...documents)
this.template.fields.forEach((field) => {
(field.areas || []).forEach((area) => {
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
area.attachment_uuid = schema[0].attachment_uuid
}
})
})
this.save()
},
moveDocument (item, direction) {
@@ -93,6 +93,11 @@ export default {
}
}
},
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: {
upload: Upload.methods.upload,
onDropFiles (e) {
+15 -13
View File
@@ -31,19 +31,21 @@
v-if="isNameFocus"
class="flex items-center relative"
>
<input
:id="`required-checkbox-${field.uuid}`"
v-model="field.required"
type="checkbox"
class="checkbox checkbox-xs no-animation rounded"
@mousedown.prevent
>
<label
:for="`required-checkbox-${field.uuid}`"
class="label text-xs"
@click.prevent="field.required = !field.required"
@mousedown.prevent
>Required</label>
<template v-if="field.type !== 'checkbox'">
<input
:id="`required-checkbox-${field.uuid}`"
v-model="field.required"
type="checkbox"
class="checkbox checkbox-xs no-animation rounded"
@mousedown.prevent
>
<label
:for="`required-checkbox-${field.uuid}`"
class="label text-xs"
@click.prevent="field.required = !field.required"
@mousedown.prevent
>Required</label>
</template>
</div>
<div
v-else
+2 -1
View File
@@ -171,7 +171,8 @@ export default {
const field = {
name: '',
uuid: v4(),
required: true,
required: type !== 'checkbox',
areas: [],
submitter_uuid: this.selectedSubmitter.uuid,
type
}
+59 -29
View File
@@ -9,39 +9,51 @@
loading="lazy"
>
<div
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute"
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
@click="$emit('scroll-to', item)"
>
<div
class="flex flex-col justify-between opacity-0 group-hover:opacity-100"
>
<div class="rounded-bl rounded-tr bg-white border">
<button
class="rounded-bl rounded-tr hover:text-base-100 hover:bg-base-content w-full transition-colors"
style="width: 24px"
@click.stop="$emit('remove', item)"
>
&times;
</button>
<div class="flex justify-between w-full">
<div style="width: 26px" />
<div class="">
<ReplaceButton
:is-direct-upload="isDirectUpload"
:template-id="template.id"
class="opacity-0 group-hover:opacity-100"
@click.stop
@success="$emit('replace', { replaceSchemaItem: item, ...$event })"
/>
</div>
<div
v-if="withArrows"
class="flex flex-col border rounded-br rounded-tl bg-white divide-y"
class="flex flex-col justify-between opacity-0 group-hover:opacity-100"
>
<button
class="rounded-tl hover:text-base-100 hover:bg-base-content w-full transition-colors"
style="width: 24px"
@click.stop="$emit('up', item)"
<div>
<button
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
style="width: 24px; height: 24px"
@click.stop="$emit('remove', item)"
>
&times;
</button>
</div>
<div
v-if="withArrows"
class="flex flex-col space-y-1"
>
&uarr;
</button>
<button
class="rounded-br hover:text-base-100 hover:bg-base-content w-full transition-colors"
style="width: 24px"
@click.stop="$emit('down', item)"
>
&darr;
</button>
<button
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
style="width: 24px; height: 24px"
@click.stop="$emit('up', item)"
>
&uarr;
</button>
<button
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
style="width: 24px; height: 24px"
@click.stop="$emit('down', item)"
>
&darr;
</button>
</div>
</div>
</div>
</div>
@@ -60,34 +72,52 @@
<script>
import Contenteditable from './contenteditable'
import Upload from './upload'
import ReplaceButton from './replace'
export default {
name: 'DocumentPreview',
components: {
Contenteditable
Contenteditable,
ReplaceButton
},
props: {
item: {
type: Object,
required: true
},
template: {
type: Object,
required: true
},
document: {
type: Object,
required: true
},
isDirectUpload: {
type: Boolean,
required: true,
default: false
},
withArrows: {
type: Boolean,
required: false,
default: true
}
},
emits: ['scroll-to', 'change', 'remove', 'up', 'down'],
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
computed: {
previewImage () {
return this.document.preview_images[0]
}
},
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: {
upload: Upload.methods.upload,
onUpdateName (value) {
this.item.name = value
@@ -0,0 +1,70 @@
<template>
<label
:for="inputId"
class="btn btn-neutral btn-xs text-white transition-none"
:class="{ 'opacity-100': isLoading || isProcessing }"
>
{{ message }}
<form
ref="form"
class="hidden"
>
<input
:id="inputId"
ref="input"
name="files[]"
type="file"
accept="image/*, application/pdf"
@change="upload"
>
</form>
</label>
</template>
<script>
import Upload from './upload'
export default {
name: 'ReplaceDocument',
props: {
templateId: {
type: [Number, String],
required: true
},
isDirectUpload: {
type: Boolean,
required: true,
default: false
}
},
emits: ['success'],
data () {
return {
isLoading: false,
isProcessing: false
}
},
computed: {
inputId () {
return 'el' + Math.random().toString(32).split('.')[1]
},
message () {
if (this.isLoading) {
return 'Uploading...'
} else if (this.isProcessing) {
return 'Processing...'
} else {
return 'Replace'
}
}
},
mounted () {
if (this.isDirectUpload) {
import('@rails/activestorage')
}
},
methods: {
upload: Upload.methods.upload
}
}
</script>
+13 -6
View File
@@ -4,12 +4,15 @@
#
# Table name: submissions
#
# id :bigint not null, primary key
# deleted_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# created_by_user_id :bigint
# template_id :bigint not null
# id :bigint not null, primary key
# deleted_at :datetime
# template_fields :text
# template_schema :text
# template_submitters :text
# created_at :datetime not null
# updated_at :datetime not null
# created_by_user_id :bigint
# template_id :bigint not null
#
# Indexes
#
@@ -27,5 +30,9 @@ class Submission < ApplicationRecord
has_many :submitters, dependent: :destroy
serialize :template_fields, JSON
serialize :template_schema, JSON
serialize :template_submitters, JSON
scope :active, -> { where(deleted_at: nil) }
end
+3
View File
@@ -44,6 +44,9 @@ class Template < ApplicationRecord
has_many_attached :documents
has_many :schema_documents, ->(e) { where(uuid: e.schema.pluck('attachment_uuid')) },
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
has_many :submissions, dependent: :destroy
scope :active, -> { where(deleted_at: nil) }
+3 -2
View File
@@ -8,7 +8,7 @@
DocuSeal
</h1>
</div>
<h2 class="mb-16 text-lg text-center text-gray-500">
<h2 class="mb-16 text-lg text-center text-gray-600">
A self-hosted and open-source web platform that provides secure and efficient digital document signing and processing.
</h2>
</div>
@@ -22,7 +22,7 @@
</div>
<h3 class="mb-4 text-2xl font-semibold">Easy to Start</h3>
<p class="text-base text-gray-500">
Run on your own host using <a href="https://hub.docker.com/r/docuseal/docuseal" class="link link-neutral font-bold" target="_blank">Docker</a> container, or deploy on your favorite managed PaaS with a single <a href="https:///www.docuseal.co/install" class="link link-neutral font-bold">click</a>.
Run on your own host using <a href="https://hub.docker.com/r/docuseal/docuseal" class="link link-neutral font-bold" target="_blank">Docker</a> container, or deploy on your favorite managed PaaS with a single <a href="https://www.docuseal.co/install" class="link link-neutral font-bold">click</a>.
</p>
</div>
</div>
@@ -70,3 +70,4 @@
</div>
</div>
</section>
<%= render 'shared/attribution' %>
+2 -2
View File
@@ -1,7 +1,7 @@
<turbo-frame id="modal">
<turbo-modal class="modal modal-open items-start !animate-none">
<turbo-modal class="modal modal-open items-start !animate-none overflow-y-auto">
<div class="absolute top-0 bottom-0 right-0 left-0" data-action="click:turbo-modal#close"></div>
<div class="modal-box pt-4 pb-6 px-6 mt-20">
<div class="modal-box pt-4 pb-6 px-6 mt-20 max-h-none">
<% if local_assigns[:title] %>
<div class="flex justify-between items-center border-b pb-2 mb-2 font-medium">
<span>
+1 -1
View File
@@ -9,7 +9,7 @@
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
</a>
</div>
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
<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') %>
+3 -3
View File
@@ -2,7 +2,7 @@
<div class="space-y-6 mx-auto">
<div class="space-y-6">
<div class="text-center w-full space-y-6">
<a href="/" class="flex justify-center">
<a href="/" class="flex justify-center items-center">
<span class="mr-3">
<%= render 'shared/logo', width: '50px', height: '50px' %>
</span>
@@ -10,14 +10,14 @@
</a>
<p class="text-xl font-semibold text-center">You have been invited to submit a form</p>
</div>
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
<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>
<p class="text-sm">Invited by <%= @template.account.name %></p>
<p class="text-sm">Invited by <span class="font-semibold"><%= @template.account.name %></span></p>
</div>
</div>
</div>
+6 -6
View File
@@ -23,7 +23,7 @@
</div>
<div class="flex md:max-h-[calc(100vh-60px)]">
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
<% @submission.template.schema.each do |item| %>
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
<% document = @submission.template.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="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
@@ -35,10 +35,10 @@
</div>
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
<div class="pr-3.5 pl-0.5">
<% fields_index = Templates.build_field_areas_index(@submission.template) %>
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<% @submission.template.schema.each do |item| %>
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
<% document = @submission.template.documents.find { |e| e.uuid == item['attachment_uuid'] } %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
@@ -57,15 +57,15 @@
</div>
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600] %>
<% submitter_fields_index = @submission.template.fields.group_by { |f| f['submitter_uuid'] } %>
<% @submission.template.submitters.each_with_index do |item, index| %>
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
<div class="flex items-center space-x-1">
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
<span class="text-lg">
<%= @submission.template.submitters.find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
</span>
</div>
<div class="flex items-center space-x-1 mt-1">
+2 -2
View File
@@ -9,7 +9,7 @@
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
</a>
</div>
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
<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') %>
@@ -23,7 +23,7 @@
</div>
<div>
<% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
<div class="divider">OR</div>
<% end %>
<download-button data-src="<%= submitter_download_index_path(@submitter.slug) %>" class="base-button w-full">
+3 -3
View File
@@ -1,4 +1,4 @@
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template) %>
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
<% values = @submitter.submission.submitters.where.not(id: @submitter.id).reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<div style="max-height: -webkit-fill-available;">
@@ -10,7 +10,7 @@
<span>DocuSeal</span>
</a>
</div>
<% @submitter.submission.template.schema.each do |item| %>
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
<% document = @submitter.submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<div class="relative my-4 shadow-md">
@@ -32,7 +32,7 @@
<div class="fixed bottom-0 w-full h-0 z-20">
<div class="mx-auto" style="max-width: 1000px">
<div class="relative md:mx-32">
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= (@submitter.submission.template_fields || @submitter.submission.template.fields).select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
</div>
</div>
</div>
+1 -1
View File
@@ -1 +1 @@
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.to_json(include: { documents: { include: { preview_images: { methods: %i[url metadata filename] } } } }) %>"></template-builder>
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder>
+1 -1
View File
@@ -37,7 +37,7 @@
<div class="space-y-4">
<% @submissions.each do |submission| %>
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
<% submitters = submission.template.submitters.filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
<% is_submission_complated = submitters.all?(&:completed_at?) %>
<% if submitters.size == 1 %>
<div>
+2 -2
View File
@@ -76,7 +76,7 @@ Rails.application.configure do
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = false
if ENV['SMTP_USERNAME']
if ENV['SMTP_ADDRESS']
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV.fetch('SMTP_ADDRESS', nil),
@@ -86,7 +86,7 @@ Rails.application.configure do
password: ENV.fetch('SMTP_PASSWORD', nil),
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'),
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
}
}.compact
end
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
Rails.configuration.to_prepare do
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
class AddTemplateFieldsToSubmission < ActiveRecord::Migration[7.0]
class MigrationTemplate < ApplicationRecord
self.table_name = 'templates'
end
class MigrationSubmission < ApplicationRecord
self.table_name = 'submissions'
end
def up
add_column :submissions, :template_fields, :text
add_column :submissions, :template_schema, :text
add_column :submissions, :template_submitters, :text
MigrationTemplate.all.each do |template|
MigrationSubmission.where(template_id: template.id).each do |submission|
submission.update_columns(template_fields: template.fields,
template_schema: template.schema,
template_submitters: template.submitters)
end
end
end
def down
remove_column :submissions, :template_fields
remove_column :submissions, :template_schema
remove_column :submissions, :template_submitters
end
end
+4 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -77,6 +77,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
t.datetime "deleted_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "template_fields"
t.text "template_schema"
t.text "template_submitters"
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["template_id"], name: "index_submissions_on_template_id"
end
+9
View File
@@ -1,4 +1,13 @@
# frozen_string_literal: true
module Submissions
module_function
def update_template_fields!(submission)
submission.template_fields = submission.template.fields
submission.template_schema = submission.template.schema
submission.template_submitters = submission.template.submitters
submission.save!
end
end
@@ -27,7 +27,7 @@ module Submissions
pdfs_index = build_pdfs_index(submitter)
template.fields.each do |field|
submitter.submission.template_fields.each do |field|
next if field['submitter_uuid'] != submitter.uuid
field.fetch('areas', []).each do |area|
@@ -159,7 +159,7 @@ module Submissions
original_documents = template.documents.preload(:blob)
results =
template.schema.map do |item|
submitter.submission.template_schema.map do |item|
pdf = pdfs_index[item['attachment_uuid']]
attachment = save_signed_pdf(pdf:, submitter:, certs:, uuid: item['attachment_uuid'], name: item['name'])
+2 -2
View File
@@ -3,10 +3,10 @@
module Templates
module_function
def build_field_areas_index(template)
def build_field_areas_index(fields)
hash = {}
template.fields.each do |field|
fields.each do |field|
(field['areas'] || []).each do |area|
hash[area['attachment_uuid']] ||= {}
acc = (hash[area['attachment_uuid']][area['page']] ||= [])
+1 -1
View File
@@ -19,7 +19,7 @@
"compression-webpack-plugin": "10.0.0",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^5.0.0",
"daisyui": "^3.1.5",
"daisyui": "^3.2.1",
"mini-css-extract-plugin": "^2.7.5",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
+14 -4
View File
@@ -2300,13 +2300,14 @@ csstype@^3.1.1:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
daisyui@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.1.5.tgz#49cb961d03504beebd238d58f0169dbe81103c95"
integrity sha512-3nk5L//IU31ISzsonKEFG8ris8hS/MRAc+PrOTOyRbssjv1fYmNKSdIrOhG6op2ED3RyJL4p6I81JUwd7RWqTw==
daisyui@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.2.1.tgz#7fac542b03023adbde2afc869518840dbef509ee"
integrity sha512-gIqE6wiqoJt9G8+n3R/SwLeUnpNCE2eDhT73rP0yZYVaM7o6zVcakBH3aEW5RGpx3UkonPiLuvcgxRcb2lE8TA==
dependencies:
colord "^2.9"
css-selector-tokenizer "^0.8"
postcss "^8"
postcss-js "^4"
tailwindcss "^3"
@@ -4448,6 +4449,15 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8:
version "8.4.26"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.1.10, postcss@^8.4.21, postcss@^8.4.23:
version "8.4.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"