rename to templates

pull/105/head
Alex Turchyn 2 years ago
parent 98fc9d964a
commit 4a303cb423

@ -1,22 +0,0 @@
# frozen_string_literal: true
module Api
class FlowsController < ApiBaseController
def update
@flow = current_account.flows.find(params[:id])
@flow.update!(flow_params)
render :ok
end
private
def flow_params
params.require(:flow).permit(:name,
schema: [%i[attachment_uuid name]],
fields: [[:uuid, :name, :type, :required,
{ options: [], areas: [%i[x y w h attachment_uuid page]] }]])
end
end
end

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Api
class TemplatesController < ApiBaseController
def update
@template = current_account.templates.find(params[:id])
@template.update!(template_params)
render :ok
end
private
def template_params
params.require(:template).permit(:name,
schema: [%i[attachment_uuid name]],
fields: [[:uuid, :name, :type, :required,
{ options: [], areas: [%i[x y w h attachment_uuid page]] }]])
end
end
end

@ -1,17 +1,17 @@
# frozen_string_literal: true
module Api
class FlowsDocumentsController < ApiBaseController
class TemplatesDocumentsController < ApiBaseController
def create
@flow = current_account.flows.find(params[:flow_id])
@template = current_account.templates.find(params[:template_id])
documents =
params[:blobs].map do |blob|
blob = ActiveStorage::Blob.find_signed(blob[:signed_id])
document = @flow.documents.create!(blob:)
document = @template.documents.create!(blob:)
Flows::ProcessDocument.call(document)
Templates::ProcessDocument.call(document)
end
schema = documents.map do |doc|

@ -2,6 +2,6 @@
class DashboardController < ApplicationController
def index
@flows = current_account.flows.active
@templates = current_account.templates.active
end
end

@ -1,38 +0,0 @@
# frozen_string_literal: true
class FlowsController < ApplicationController
layout false
def show
@flow = current_account.flows.preload(documents_attachments: { preview_images_attachments: :blob })
.find(params[:id])
end
def new
@flow = current_account.flows.new
end
def create
@flow = current_account.flows.new(flow_params)
@flow.author = current_user
if @flow.save
redirect_to flow_path(@flow)
else
render turbo_stream: turbo_stream.replace(:modal, template: 'flows/new'), status: :unprocessable_entity
end
end
def destroy
@flow = current_account.flows.find(params[:id])
@flow.update!(deleted_at: Time.current)
redirect_to settings_users_path, notice: 'Flow has been archived.'
end
private
def flow_params
params.require(:flow).permit(:name, :schema)
end
end

@ -1,7 +1,7 @@
# frozen_string_literal: true
class SendSubmissionEmailController < ApplicationController
layout 'flow'
layout 'form'
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
@ -10,8 +10,8 @@ class SendSubmissionEmailController < ApplicationController
def create
@submission =
if params[:flow_slug]
Submission.joins(:flow).find_by!(email: params[:email], flow: { slug: params[:flow_slug] })
if params[:template_slug]
Submission.joins(:template).find_by!(email: params[:email], template: { slug: params[:template_slug] })
else
Submission.find_by!(slug: params[:submission_slug])
end

@ -1,23 +1,23 @@
# frozen_string_literal: true
class StartFlowController < ApplicationController
layout 'flow'
class StartFormController < ApplicationController
layout 'form'
skip_before_action :authenticate_user!
before_action :load_flow
before_action :load_template
def show
@submission = @flow.submissions.new
@submission = @template.submissions.new
end
def update
@submission = @flow.submissions.find_or_initialize_by(
@submission = @template.submissions.find_or_initialize_by(
deleted_at: nil, **submission_params
)
if @submission.completed_at?
redirect_to start_flow_completed_path(@flow.slug, email: submission_params[:email])
redirect_to start_form_completed_path(@template.slug, email: submission_params[:email])
else
@submission.assign_attributes(
opened_at: Time.current,
@ -26,7 +26,7 @@ class StartFlowController < ApplicationController
)
if @submission.save
redirect_to submit_flow_path(@submission.slug)
redirect_to submit_form_path(@submission.slug)
else
render :show
end
@ -34,7 +34,7 @@ class StartFlowController < ApplicationController
end
def completed
@submission = @flow.submissions.find_by(email: params[:email])
@submission = @template.submissions.find_by(email: params[:email])
end
private
@ -43,9 +43,9 @@ class StartFlowController < ApplicationController
params.require(:submission).permit(:email)
end
def load_flow
slug = params[:slug] || params[:start_flow_slug]
def load_template
slug = params[:slug] || params[:start_template_slug]
@flow = Flow.find_by!(slug:)
@template = Template.find_by!(slug:)
end
end

@ -1,16 +1,16 @@
# frozen_string_literal: true
class SubmissionsController < ApplicationController
before_action :load_flow, only: %i[index new create]
before_action :load_template, only: %i[index new create]
def index
@submissions = @flow.submissions.active
@submissions = @template.submissions.active
end
def show
@submission =
Submission.joins(:flow).where(flow: { account_id: current_account.id })
.preload(flow: { documents_attachments: { preview_images_attachments: :blob } })
Submission.joins(:template).where(template: { account_id: current_account.id })
.preload(template: { documents_attachments: { preview_images_attachments: :blob } })
.find(params[:id])
end
@ -21,7 +21,7 @@ class SubmissionsController < ApplicationController
submissions =
emails.map do |email|
submission = @flow.submissions.create!(email:, sent_at: params[:send_email] == '1' ? Time.current : nil)
submission = @template.submissions.create!(email:, sent_at: params[:send_email] == '1' ? Time.current : nil)
if params[:send_email] == '1'
SubmissionMailer.invitation_email(submission, message: params[:message]).deliver_later!
@ -30,21 +30,21 @@ class SubmissionsController < ApplicationController
submission
end
redirect_to flow_submissions_path(@flow), notice: "#{submissions.size} recepients added"
redirect_to template_submissions_path(@template), notice: "#{submissions.size} recepients added"
end
def destroy
submission = Submission.joins(:flow).where(flow: { account_id: current_account.id })
submission = Submission.joins(:template).where(template: { account_id: current_account.id })
.find(params[:id])
submission.update!(deleted_at: Time.current)
redirect_to flow_submissions_path(submission.flow), notice: 'Submission has been archieved'
redirect_to template_submissions_path(submission.template), notice: 'Submission has been archieved'
end
private
def load_flow
@flow = current_account.flows.find(params[:flow_id])
def load_template
@template = current_account.templates.find(params[:template_id])
end
end

@ -1,18 +1,18 @@
# frozen_string_literal: true
class SubmissionsDebugController < ApplicationController
layout 'flow'
layout 'form'
skip_before_action :authenticate_user!
def index
@submission = Submission.preload({ attachments_attachments: :blob },
flow: { documents_attachments: :blob })
template: { documents_attachments: :blob })
.find_by(slug: params[:submission_slug])
respond_to do |f|
f.html do
render 'submit_flow/show'
render 'submit_template/show'
end
f.pdf do
Submissions::GenerateResultAttachments.call(@submission)

@ -1,15 +1,15 @@
# frozen_string_literal: true
class SubmitFlowController < ApplicationController
layout 'flow'
class SubmitFormController < ApplicationController
layout 'form'
skip_before_action :authenticate_user!
def show
@submission = Submission.preload(flow: { documents_attachments: { preview_images_attachments: :blob } })
@submission = Submission.preload(template: { documents_attachments: { preview_images_attachments: :blob } })
.find_by!(slug: params[:slug])
return redirect_to submit_flow_completed_path(@submission.slug) if @submission.completed_at?
return redirect_to submit_form_completed_path(@submission.slug) if @submission.completed_at?
end
def update
@ -23,7 +23,7 @@ class SubmitFlowController < ApplicationController
end
def completed
@submission = Submission.find_by!(slug: params[:submit_flow_slug])
@submission = Submission.find_by!(slug: params[:submit_form_slug])
end
private

@ -0,0 +1,38 @@
# frozen_string_literal: true
class TemplatesController < ApplicationController
layout false
def show
@template = current_account.templates.preload(documents_attachments: { preview_images_attachments: :blob })
.find(params[:id])
end
def new
@template = current_account.templates.new
end
def create
@template = current_account.templates.new(template_params)
@template.author = current_user
if @template.save
redirect_to template_path(@template)
else
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_entity
end
end
def destroy
@template = current_account.templates.find(params[:id])
@template.update!(deleted_at: Time.current)
redirect_to settings_users_path, notice: 'template has been archived.'
end
private
def template_params
params.require(:template).permit(:name, :schema)
end
end

@ -8,7 +8,7 @@ import TurboModal from './elements/turbo_modal'
import FileDropzone from './elements/file_dropzone'
import MenuActive from './elements/menu_active'
import FlowBuilder from './flow_builder/builder'
import TemplateBuilder from './template_builder/builder'
document.addEventListener('turbo:before-cache', () => {
window.flash?.remove()
@ -20,12 +20,12 @@ window.customElements.define('turbo-modal', TurboModal)
window.customElements.define('file-dropzone', FileDropzone)
window.customElements.define('menu-active', MenuActive)
window.customElements.define('flow-builder', class extends HTMLElement {
window.customElements.define('template-builder', class extends HTMLElement {
connectedCallback () {
this.appElem = document.createElement('div')
this.app = createApp(FlowBuilder, {
flow: reactive(JSON.parse(this.dataset.flow))
this.app = createApp(TemplateBuilder, {
template: reactive(JSON.parse(this.dataset.template))
})
this.app.mount(this.appElem)

@ -1,12 +1,12 @@
import { createApp, reactive } from 'vue'
import Flow from './flow_form/form'
import Form from './submission_form/form'
window.customElements.define('flow-form', class extends HTMLElement {
window.customElements.define('submission-form', class extends HTMLElement {
connectedCallback () {
this.appElem = document.createElement('div')
this.app = createApp(Flow, {
this.app = createApp(Form, {
submissionSlug: this.dataset.submissionSlug,
authenticityToken: this.dataset.authenticityToken,
values: reactive(JSON.parse(this.dataset.values)),

@ -1,4 +1,4 @@
@config "../../tailwind.flow.config.js";
@config "../../tailwind.form.config.js";
@import "tailwindcss/base";
@import "tailwindcss/components";

@ -164,7 +164,7 @@ import CheckboxStep from './checkbox_step'
import FormCompleted from './completed'
export default {
name: 'FlowForm',
name: 'SubmissionForm',
components: {
FieldAreas,
ImageStep,

@ -5,13 +5,13 @@
>
<div class="flex justify-between py-1.5 items-center">
<Contenteditable
:model-value="flow.name"
:model-value="template.name"
class="text-3xl focus:text-clip"
@update:model-value="updateName"
/>
<div class="space-x-3 flex items-center">
<a
:href="`/flows/${flow.id}/submissions`"
:href="`/templates/${template.id}/submissions`"
class="btn btn-primary"
>
<IconUsersPlus
@ -40,9 +40,9 @@
class="overflow-auto w-52 flex-none pr-4 mt-0.5 pt-0.5"
>
<DocumentPreview
v-for="(item, index) in flow.schema"
v-for="(item, index) in template.schema"
:key="index"
:with-arrows="flow.schema.length > 1"
:with-arrows="template.schema.length > 1"
:item="item"
:document="sortedDocuments[index]"
@scroll-to="scrollIntoDocument(item)"
@ -53,7 +53,7 @@
/>
<div class="sticky bottom-0 bg-base-100 py-2">
<Upload
:flow-id="flow.id"
:template-id="template.id"
@success="updateFromUpload"
/>
</div>
@ -90,7 +90,7 @@
FIelds
<Fields
ref="fields"
v-model:fields="flow.fields"
v-model:fields="template.fields"
@set-draw="drawField = $event"
@set-drag="dragFieldType = $event"
@drag-end="dragFieldType = null"
@ -110,7 +110,7 @@ import DocumentPreview from './preview'
import { IconUsersPlus, IconDeviceFloppy } from '@tabler/icons-vue'
export default {
name: 'FlowBuilder',
name: 'TemplateBuilder',
components: {
Upload,
Document,
@ -121,7 +121,7 @@ export default {
IconDeviceFloppy
},
props: {
flow: {
template: {
type: Object,
required: true
}
@ -138,7 +138,7 @@ export default {
fieldAreasIndex () {
const areas = {}
this.flow.fields.forEach((f) => {
this.template.fields.forEach((f) => {
(f.areas || []).forEach((a) => {
areas[a.attachment_uuid] ||= {}
@ -151,8 +151,8 @@ export default {
return areas
},
sortedDocuments () {
return this.flow.schema.map((item) => {
return this.flow.documents.find(doc => doc.uuid === item.attachment_uuid)
return this.template.schema.map((item) => {
return this.template.documents.find(doc => doc.uuid === item.attachment_uuid)
})
}
},
@ -191,8 +191,8 @@ export default {
this.$refs.fields.addField(this.dragFieldType, area)
},
updateFromUpload ({ schema, documents }) {
this.flow.schema.push(...schema)
this.flow.documents.push(...documents)
this.template.schema.push(...schema)
this.template.documents.push(...documents)
this.$nextTick(() => {
this.$refs.previews.scrollTop = this.$refs.previews.scrollHeight
@ -203,28 +203,28 @@ export default {
this.save()
},
updateName (value) {
this.flow.name = value
this.template.name = value
this.save()
},
onDocumentRemove (item) {
if (window.confirm('Are you sure?')) {
this.flow.schema.splice(this.flow.schema.indexOf(item), 1)
this.template.schema.splice(this.template.schema.indexOf(item), 1)
}
this.save()
},
moveDocument (item, direction) {
const currentIndex = this.flow.schema.indexOf(item)
const currentIndex = this.template.schema.indexOf(item)
this.flow.schema.splice(currentIndex, 1)
this.template.schema.splice(currentIndex, 1)
if (currentIndex + direction > this.flow.schema.length) {
this.flow.schema.unshift(item)
if (currentIndex + direction > this.template.schema.length) {
this.template.schema.unshift(item)
} else if (currentIndex + direction < 0) {
this.flow.schema.push(item)
this.template.schema.push(item)
} else {
this.flow.schema.splice(currentIndex + direction, 0, item)
this.template.schema.splice(currentIndex + direction, 0, item)
}
this.save()
@ -239,9 +239,9 @@ export default {
})
},
save () {
return fetch(`/api/flows/${this.flow.id}`, {
return fetch(`/api/templates/${this.template.id}`, {
method: 'PUT',
body: JSON.stringify({ flow: this.flow }),
body: JSON.stringify({ template: this.template }),
headers: { 'Content-Type': 'application/json' }
}).then((resp) => {
console.log(resp)

@ -18,7 +18,7 @@
import Page from './page'
export default {
name: 'FlowDocument',
name: 'TemplateDocument',
components: {
Page
},

@ -67,7 +67,7 @@
<script>
export default {
name: 'FlowField',
name: 'TemplateField',
props: {
field: {
type: Object,

@ -49,7 +49,7 @@ import Field from './field'
import { v4 } from 'uuid'
export default {
name: 'FlowFields',
name: 'TemplateFields',
components: {
Field
},

@ -44,7 +44,7 @@
import FieldArea from './area'
export default {
name: 'FlowPage',
name: 'TemplatePage',
components: {
FieldArea
},

@ -32,7 +32,7 @@ export default {
IconUpload
},
props: {
flowId: {
templateId: {
type: [Number, String],
required: true
}
@ -76,7 +76,7 @@ export default {
})
)
fetch(`/api/flows/${this.flowId}/documents`, {
fetch(`/api/templates/${this.templateId}/documents`, {
method: 'POST',
body: JSON.stringify({ blobs }),
headers: { 'Content-Type': 'application/json' }

@ -12,7 +12,7 @@
class Account < ApplicationRecord
has_many :users, dependent: :destroy
has_many :encrypted_configs, dependent: :destroy
has_many :flows, dependent: :destroy
has_many :templates, dependent: :destroy
has_many :active_users, -> { active }, dependent: :destroy,
inverse_of: :account, class_name: 'User'
end

@ -16,20 +16,20 @@
# values :string not null
# created_at :datetime not null
# updated_at :datetime not null
# flow_id :bigint not null
# template_id :bigint not null
#
# Indexes
#
# index_submissions_on_email (email)
# index_submissions_on_flow_id (flow_id)
# index_submissions_on_slug (slug) UNIQUE
# index_submissions_on_template_id (template_id)
#
# Foreign Keys
#
# fk_rails_... (flow_id => flows.id)
# fk_rails_... (template_id => templates.id)
#
class Submission < ApplicationRecord
belongs_to :flow
belongs_to :template
attribute :values, :string, default: -> { {} }
attribute :slug, :string, default: -> { SecureRandom.base58(8) }

@ -2,7 +2,7 @@
# == Schema Information
#
# Table name: flows
# Table name: templates
#
# id :bigint not null, primary key
# deleted_at :datetime
@ -17,16 +17,16 @@
#
# Indexes
#
# index_flows_on_account_id (account_id)
# index_flows_on_author_id (author_id)
# index_flows_on_slug (slug) UNIQUE
# index_templates_on_account_id (account_id)
# index_templates_on_author_id (author_id)
# index_templates_on_slug (slug) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (author_id => users.id)
#
class Flow < ApplicationRecord
class Template < ApplicationRecord
belongs_to :author, class_name: 'User'
belongs_to :account

@ -1,10 +1,10 @@
<div class="max-w-6xl mx-auto px-2 py-3">
<%= link_to 'Create Flow', new_flow_path, data: { turbo_frame: :modal } %>
<% @flows.each do |flow| %>
<%= link_to 'Create Template', new_template_path, data: { turbo_frame: :modal } %>
<% @templates.each do |template| %>
<div>
<%= flow.name %> |
<a href="<%= flow_path(flow) %>">edit</a> |
<a href="<%= flow_submissions_path(flow) %>">submissions</a> |
<%= template.name %> |
<a href="<%= template_path(template) %>">edit</a> |
<a href="<%= template_submissions_path(template) %>">submissions</a> |
</div>
<% end %>
</div>

@ -1,10 +0,0 @@
<%= render 'shared/turbo_modal', title: 'New Flow' do %>
<%= form_for @flow, data: { turbo_frame: :_top } do |f| %>
<div class="form-control my-6">
<%= f.text_field :name, required: true, placeholder: 'Flow Name', class: 'base-input' %>
</div>
<div class="form-control mt-4">
<%= f.button button_title('Create'), class: 'base-button' %>
</div>
<% end %>
<% end %>

@ -7,8 +7,8 @@
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= javascript_pack_tag 'flow', defer: true %>
<%= stylesheet_pack_tag 'flow', media: 'all' %>
<%= javascript_pack_tag 'form', defer: true %>
<%= stylesheet_pack_tag 'form', media: 'all' %>
<%= yield :head %>
</head>
<body class="font-sans antialiased font-normal leading-normal bg-white text-gray-700">

@ -1,9 +0,0 @@
You have been invited to submit flow <%= @flow.name %>
<%= form_for @submission, url: start_flow_path(@flow.slug), data: { turbo_frame: :_top }, method: :put do |f| %>
Provide youe email to start
<div>
<%= f.label :email %>
<%= f.email_field :email, required: true %>
</div>
<%= f.button button_title %>
<% end %>

@ -1,5 +1,5 @@
<p>
Form has been submitted alredy by ypu - thanks!
</p>
<%= button_to button_title('Send copy to Email'), send_submission_email_index_path, params: { flow_slug: @flow.slug, email: params[:email] }, form: { onsubmit: 'event.submitter.disabled = true' } %>
<%= button_to button_title('Send copy to Email'), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, form: { onsubmit: 'event.submitter.disabled = true' } %>
<%# do not allow donwload for securetiy reaosn<a href="">Download documets</a> %>

@ -0,0 +1,9 @@
You have been invited to submit template <%= @template.name %>
<%= form_for @submission, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put do |f| %>
Provide youe email to start
<div>
<%= f.label :email %>
<%= f.email_field :email, required: true %>
</div>
<%= f.button button_title %>
<% end %>

@ -1,4 +1,4 @@
<p>Hi there</p>
<p>You have been invited to submit a form:</p>
<p><%= link_to 'Submit', submit_flow_index_url(slug: @submission.slug) %></p>
<p><%= link_to 'Submit', submit_form_index_url(slug: @submission.slug) %></p>
<p>If you didn't request this, please ignore this email.</p>

@ -1,8 +1,8 @@
Submissions
Flow <%= @flow.name %>
template <%= @template.name %>
Copy share link:
<input autocomplete="off" type="text" class="w-full" value="<%= start_flow_url(slug: @flow.slug) %>" disabled>
<a href="<%= new_flow_submission_path(@flow) %>" class="bg-green-600" data-turbo-frame="modal">Add Recepients</a>
<input autocomplete="off" type="text" class="w-full" value="<%= start_form_url(slug: @template.slug) %>" disabled>
<a href="<%= new_template_submission_path(@template) %>" class="bg-green-600" data-turbo-frame="modal">Add Recepients</a>
<table>
<tr>
<th>
@ -32,7 +32,7 @@ Copy share link:
</td>
<td>
copy link<br>
<%= link_to 'View', submission_path(@flow) %>
<%= link_to 'View', submission_path(@template) %>
<%= button_to 'Remove', submission_path(submission), method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
</td>
</tr>

@ -1,5 +1,5 @@
<%= render 'shared/turbo_modal' do %>
<%= form_for '', url: flow_submissions_path(@flow), data: { turbo_frame: :_top } do |f| %>
<%= form_for '', url: template_submissions_path(@template), data: { turbo_frame: :_top } do |f| %>
<div>
<%= f.label :emails %>
<%= f.text_area :emails, required: true %>

@ -1,8 +1,8 @@
Flow: <%= @submission.flow.name %>
template: <%= @submission.template.name %>
Sub: <%= @submission.slug %>
Copy share link:
<input autocomplete="off" type="text" class="w-full" value="<%= submit_flow_url(slug: @submission.slug) %>" disabled>
<% @submission.flow.fields.each do |field| %>
<input autocomplete="off" type="text" class="w-full" value="<%= submit_form_url(slug: @submission.slug) %>" disabled>
<% @submission.template.fields.each do |field| %>
<div>
<%= field['name'] %>:
<% if ['image', 'signature'].include?(field['type']) %>

@ -1,18 +0,0 @@
<% attachment_field_uuids = @submission.flow.fields.select { |f| f['type'].in?(%w[image signature attachment]) }.pluck('uuid') %>
<% attachments = ActiveStorage::Attachment.where(uuid: @submission.values.values_at(*attachment_field_uuids).flatten).preload(:blob) %>
<flow-view class="mx-auto block" style="max-width: 1000px">
<% @submission.flow.schema.each do |item| %>
<% document = @submission.flow.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">
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>" loading="lazy">
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute"></div>
</div>
<% end %>
<% end %>
<div class="sticky bottom-8 w-full">
<div class="bg-white mx-8 md:mx-32 border p-4 rounded">
<flow-form data-submission-slug="<%= @submission.slug %>" data-attachments="<%= attachments.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submission.flow.fields.to_json %>" data-values="<%= @submission.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></flow-form>
</div>
</div>
</flow-view>

@ -0,0 +1,18 @@
<% attachment_field_uuids = @submission.template.fields.select { |f| f['type'].in?(%w[image signature attachment]) }.pluck('uuid') %>
<% attachments = ActiveStorage::Attachment.where(uuid: @submission.values.values_at(*attachment_field_uuids).flatten).preload(:blob) %>
<div class="mx-auto block" style="max-width: 1000px">
<% @submission.template.schema.each do |item| %>
<% document = @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">
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>" loading="lazy">
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute"></div>
</div>
<% end %>
<% end %>
<div class="sticky bottom-8 w-full">
<div class="bg-white mx-8 md:mx-32 border p-4 rounded">
<submission-form data-submission-slug="<%= @submission.slug %>" data-attachments="<%= attachments.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submission.template.fields.to_json %>" data-values="<%= @submission.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
</div>
</div>
</div>

@ -0,0 +1,10 @@
<%= render 'shared/turbo_modal', title: 'New Template' do %>
<%= form_for @template, data: { turbo_frame: :_top } do |f| %>
<div class="form-control my-6">
<%= f.text_field :name, required: true, placeholder: 'Template Name', class: 'base-input' %>
</div>
<div class="form-control mt-4">
<%= f.button button_title('Create'), class: 'base-button' %>
</div>
<% end %>
<% end %>

@ -11,6 +11,6 @@
<%= stylesheet_pack_tag 'application', media: 'all' %>
</head>
<body class="h-full" data>
<flow-builder data-flow="<%= @flow.to_json(include: { documents: { include: { preview_images: { methods: %i[url metadata filename] } } } }) %>"></flow-builder>
<template-builder data-template="<%= @template.to_json(include: { documents: { include: { preview_images: { methods: %i[url metadata filename] } } } }) %>"></template-builder>
</body>
</html>

@ -21,8 +21,8 @@ Rails.application.routes.draw do
namespace :api do
resources :attachments, only: %i[create]
resources :flows, only: %i[update] do
resources :documents, only: %i[create destroy], controller: 'flows_documents'
resources :templates, only: %i[update] do
resources :documents, only: %i[create destroy], controller: 'templates_documents'
end
end
@ -30,15 +30,15 @@ Rails.application.routes.draw do
resources :setup, only: %i[index create]
resources :users, only: %i[new create edit update destroy]
resources :submissions, only: %i[show destroy]
resources :flows, only: %i[new create show destroy] do
resources :templates, only: %i[new create show destroy] do
resources :submissions, only: %i[index new create]
end
resources :start_flow, only: %i[show update], path: 'f', param: 'slug' do
resources :start_form, only: %i[show update], path: 'f', param: 'slug' do
get :completed
end
resources :submit_flow, only: %i[show update], path: 'l', param: 'slug' do
resources :submit_form, only: %i[show update], path: 'l', param: 'slug' do
get :completed
end

@ -1,8 +1,8 @@
# frozen_string_literal: true
class CreateFlows < ActiveRecord::Migration[7.0]
class CreateTemplates < ActiveRecord::Migration[7.0]
def change
create_table :flows do |t|
create_table :templates do |t|
t.string :slug, null: false, index: { unique: true }
t.string :name, null: false
t.string :schema, null: false

@ -5,7 +5,7 @@ class CreateSubmissions < ActiveRecord::Migration[7.0]
create_table :submissions do |t|
t.string :email, null: false, index: true
t.string :slug, null: false, index: { unique: true }
t.references :flow, null: false, foreign_key: true, index: true
t.references :template, null: false, foreign_key: true, index: true
t.string :values, null: false
t.string :ua
t.string :ip

@ -60,25 +60,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do
t.index ["account_id"], name: "index_encrypted_configs_on_account_id"
end
create_table "flows", force: :cascade do |t|
t.string "slug", null: false
t.string "name", null: false
t.string "schema", null: false
t.string "fields", null: false
t.bigint "author_id", null: false
t.bigint "account_id", null: false
t.datetime "deleted_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_flows_on_account_id"
t.index ["author_id"], name: "index_flows_on_author_id"
t.index ["slug"], name: "index_flows_on_slug", unique: true
end
create_table "submissions", force: :cascade do |t|
t.string "email", null: false
t.string "slug", null: false
t.bigint "flow_id", null: false
t.bigint "template_id", null: false
t.string "values", null: false
t.string "ua"
t.string "ip"
@ -89,8 +74,23 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_submissions_on_email"
t.index ["flow_id"], name: "index_submissions_on_flow_id"
t.index ["slug"], name: "index_submissions_on_slug", unique: true
t.index ["template_id"], name: "index_submissions_on_template_id"
end
create_table "templates", force: :cascade do |t|
t.string "slug", null: false
t.string "name", null: false
t.string "schema", null: false
t.string "fields", null: false
t.bigint "author_id", null: false
t.bigint "account_id", null: false
t.datetime "deleted_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_templates_on_account_id"
t.index ["author_id"], name: "index_templates_on_author_id"
t.index ["slug"], name: "index_templates_on_slug", unique: true
end
create_table "users", force: :cascade do |t|
@ -123,8 +123,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "encrypted_configs", "accounts"
add_foreign_key "flows", "accounts"
add_foreign_key "flows", "users", column: "author_id"
add_foreign_key "submissions", "flows"
add_foreign_key "submissions", "templates"
add_foreign_key "templates", "accounts"
add_foreign_key "templates", "users", column: "author_id"
add_foreign_key "users", "accounts"
end

@ -1,20 +0,0 @@
# frozen_string_literal: true
module Flows
module_function
def build_field_areas_index(flow)
hash = {}
flow.fields.each do |field|
(field['areas'] || []).each do |area|
hash[area['attachment_uuid']] ||= {}
acc = (hash[area['attachment_uuid']][area['page']] ||= [])
acc << { area:, field: }
end
end
hash
end
end

@ -9,18 +9,18 @@ module Submissions
# rubocop:disable Metrics
def call(submission)
cert = submission.flow.account.encrypted_configs
cert = submission.template.account.encrypted_configs
.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
zip_file = Tempfile.new
zip_stream = Zip::ZipOutputStream.open(zip_file)
pdfs_index =
submission.flow.documents.to_h do |attachment|
submission.template.documents.to_h do |attachment|
[attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))]
end
submission.flow.fields.each do |field|
submission.template.fields.each do |field|
field.fetch('areas', []).each do |area|
pdf = pdfs_index[area['attachment_uuid']]
@ -96,29 +96,29 @@ module Submissions
end
end
submission.flow.schema.map do |item|
document = submission.flow.documents.find { |a| a.uuid == item['attachment_uuid'] }
submission.template.schema.map do |item|
document = submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] }
io = StringIO.new
zip_stream.put_next_entry("#{item['name']}.pdf")
zip_stream.write(io.string)
pdf = pdfs_index[item['attachment_uuid']]
pdf.sign(io, reason: "Signed by #{submission.email}",
doc_mdp_permissions: :no_changes,
# doc_mdp_permissions: :no_changes,
certificate: OpenSSL::X509::Certificate.new(cert['cert']),
key: OpenSSL::PKey::RSA.new(cert['key']),
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
OpenSSL::X509::Certificate.new(cert['root_ca'])])
zip_stream.put_next_entry("#{item['name']}.pdf")
zip_stream.write(io.string)
submission.documents.attach(io: StringIO.new(io.string), filename: document.filename)
end
zip_stream.close
submission.archive.attach(io: zip_file, filename: "#{submission.flow.name}.zip")
submission.archive.attach(io: zip_file, filename: "#{submission.template.name}.zip")
end
# rubocop:enable Metrics
end

@ -0,0 +1,4 @@
# frozen_string_literal: true
module Templates
end

@ -1,6 +1,6 @@
# frozen_string_literal: true
module Flows
module Templates
module ProcessDocument
DPI = 200
FORMAT = '.jpg'

@ -3,9 +3,9 @@ const baseConfigs = require('./tailwind.config.js')
module.exports = {
...baseConfigs,
content: [
'./app/javascript/flow_form/**/*.vue',
'./app/views/submit_flow/**/*.erb',
'./app/views/start_flow/**/*.erb',
'./app/javascript/submission_form/**/*.vue',
'./app/views/submit_form/**/*.erb',
'./app/views/start_form/**/*.erb',
'./app/views/send_submission_copy/**/*.erb'
]
}
Loading…
Cancel
Save