style template show page

pull/105/head
Alex Turchyn 2 years ago
parent ed157dc952
commit d0c7d449d5

@ -11,6 +11,10 @@ class ApplicationController < ActionController::Base
:current_account, :current_account,
:svg_icon :svg_icon
rescue_from Pagy::OverflowError do
redirect_to request.path
end
def default_url_options def default_url_options
Docuseal.default_url_options Docuseal.default_url_options
end end

@ -47,7 +47,7 @@ class SubmissionsController < ApplicationController
emails = params[:emails].to_s.scan(User::EMAIL_REGEXP) emails = params[:emails].to_s.scan(User::EMAIL_REGEXP)
emails.map do |email| emails.map do |email|
submission = @template.submissions.new submission = @template.submissions.new(created_by_user: current_user)
submission.submitters.new(email:, uuid: @template.submitters.first['uuid'], submission.submitters.new(email:, uuid: @template.submitters.first['uuid'],
sent_at: params[:send_email] == '1' ? Time.current : nil) sent_at: params[:send_email] == '1' ? Time.current : nil)
@ -57,7 +57,7 @@ class SubmissionsController < ApplicationController
def create_submissions_from_submitters def create_submissions_from_submitters
submissions_params[:submission].to_h.map do |_, attrs| submissions_params[:submission].to_h.map do |_, attrs|
submission = @template.submissions.new submission = @template.submissions.new(created_by_user: current_user)
attrs[:submitters].each do |submitter_attrs| attrs[:submitters].each do |submitter_attrs|
submission.submitters.new(**submitter_attrs, sent_at: params[:send_email] == '1' ? Time.current : nil) submission.submitters.new(**submitter_attrs, sent_at: params[:send_email] == '1' ? Time.current : nil)

@ -4,9 +4,11 @@ class TemplatesController < ApplicationController
before_action :load_base_template, only: %i[new create] before_action :load_base_template, only: %i[new create]
def show def show
@template = current_account.templates.find(params[:id]) @template = current_account.templates.active.find(params[:id])
@pagy, @submissions = pagy(@template.submissions.active) @pagy, @submissions = pagy(@template.submissions.active.preload(:submitters).order(id: :desc))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end end
def new def new

@ -1,152 +1,15 @@
// Source: https://github.com/github/clipboard-copy-element
// License: MIT
export default class extends HTMLElement { export default class extends HTMLElement {
constructor () {
super()
this.addEventListener('click', clicked)
this.addEventListener('focus', focused)
this.addEventListener('blur', blurred)
}
connectedCallback () { connectedCallback () {
if (!this.hasAttribute('tabindex')) { this.addEventListener('click', (e) => {
this.setAttribute('tabindex', '0') e.stopPropagation()
}
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'button')
}
}
get value () {
return this.getAttribute('value') || ''
}
set value (text) {
this.setAttribute('value', text)
}
}
function createNode (text) {
const node = document.createElement('pre')
node.style.width = '1px'
node.style.height = '1px'
node.style.position = 'fixed'
node.style.top = '5px'
node.textContent = text
return node
}
function copyNode (node) { navigator.clipboard.writeText(this.dataset.text || this.innerText.trim())
if ('clipboard' in navigator) { })
return navigator.clipboard.writeText(node.textContent || '')
} }
const selection = getSelection() disconnectedCallback () {
if (selection == null) { this.querySelectorAll('input').forEach((e) => {
return Promise.reject(new Error()) e.checked = false
} })
selection.removeAllRanges()
const range = document.createRange()
range.selectNodeContents(node)
selection.addRange(range)
document.execCommand('copy')
selection.removeAllRanges()
return Promise.resolve()
}
function copyText (text) {
if ('clipboard' in navigator) {
return navigator.clipboard.writeText(text)
}
const body = document.body
if (!body) {
return Promise.reject(new Error())
}
const node = createNode(text)
body.appendChild(node)
copyNode(node)
body.removeChild(node)
return Promise.resolve()
}
function copyTarget (content) {
if (
content instanceof HTMLInputElement ||
content instanceof HTMLTextAreaElement
) {
return copyText(content.value)
} else if (
content instanceof HTMLAnchorElement &&
content.hasAttribute('href')
) {
return copyText(content.href)
} else {
return copyNode(content)
}
} }
async function copy (button) {
const id = button.getAttribute('for')
const text = button.getAttribute('value')
function trigger () {
button.dispatchEvent(new CustomEvent('clipboard-copy', { bubbles: true }))
}
function toggleActiveIcon () {
if (button.classList.contains('swap')) {
button.classList.toggle('swap-active')
}
}
if (text) {
await copyText(text)
trigger()
toggleActiveIcon()
} else if (id) {
const root = 'getRootNode' in Element.prototype ? button.getRootNode() : button.ownerDocument
if (!(root instanceof Document || ('ShadowRoot' in window && root instanceof ShadowRoot))) return
const node = root.getElementById(id)
if (node) {
await copyTarget(node)
trigger()
toggleActiveIcon()
}
}
}
function clicked (event) {
const button = event.currentTarget
if (button instanceof HTMLElement) {
copy(button)
}
}
function keydown (event) {
if (event.key === ' ' || event.key === 'Enter') {
const button = event.currentTarget
if (button instanceof HTMLElement) {
event.preventDefault()
copy(button)
}
}
}
function focused (event) {
event.currentTarget.addEventListener('keydown', keydown)
}
function blurred (event) {
event.currentTarget.removeEventListener('keydown', keydown)
} }

@ -8,18 +8,22 @@
# deleted_at :datetime # deleted_at :datetime
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# created_by_user_id :bigint
# template_id :bigint not null # template_id :bigint not null
# #
# Indexes # Indexes
# #
# index_submissions_on_created_by_user_id (created_by_user_id)
# index_submissions_on_template_id (template_id) # index_submissions_on_template_id (template_id)
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (created_by_user_id => users.id)
# fk_rails_... (template_id => templates.id) # fk_rails_... (template_id => templates.id)
# #
class Submission < ApplicationRecord class Submission < ApplicationRecord
belongs_to :template belongs_to :template
belongs_to :created_by_user, class_name: 'User', optional: true
has_many :submitters, dependent: :destroy has_many :submitters, dependent: :destroy

@ -1,8 +1,8 @@
<% if @templates.any? %> <% if @templates.any? %>
<div class="flex justify-between mb-4"> <div class="flex justify-between mb-4 items-center">
<h1 class="text-4xl font-bold">Templates</h1> <h1 class="text-4xl font-bold">Templates</h1>
<%= link_to new_template_path, class: 'btn btn-primary btn-md gap-2', data: { turbo_frame: :modal } do %> <%= link_to new_template_path, class: 'btn btn-primary text-base btn-md gap-2', data: { turbo_frame: :modal } do %>
<%= svg_icon('plus', class: 'w-6 h-6') %> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<span class="hidden md:block">Create</span> <span class="hidden md:block">Create</span>
<% end %> <% end %>
</div> </div>
@ -25,13 +25,13 @@
</div> </div>
</a> </a>
<div class="absolute top-0 bottom-0 w-0 pt-7 space-y-1.5 hidden group-hover:block" style="right: 40px"> <div class="absolute top-0 bottom-0 w-0 pt-7 space-y-1.5 hidden group-hover:block" style="right: 40px">
<a href="<%= edit_template_path(template) %>" class="btn btn-xs btn-outline bg-base-200 btn-circle"> <a href="<%= edit_template_path(template) %>" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
<%= svg_icon('pencil', class: 'w-4 h-4') %> <%= svg_icon('pencil', class: 'w-4 h-4') %>
</a> </a>
<a href="<%= new_template_path(base_template_id: template.id) %>" data-turbo-frame="modal" class="btn btn-xs btn-outline bg-base-200 btn-circle"> <a href="<%= new_template_path(base_template_id: template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
<%= svg_icon('copy', class: 'w-4 h-4') %> <%= svg_icon('copy', class: 'w-4 h-4') %>
</a> </a>
<%= button_to template_path(template), data: { turbo_confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-xs btn-outline bg-base-200 btn-circle' do %> <%= button_to template_path(template), data: { turbo_confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-xs hover:btn-outline bg-base-200 btn-circle' do %>
<%= svg_icon('trash', class: 'w-4 h-4 enabled') %> <%= svg_icon('trash', class: 'w-4 h-4 enabled') %>
<%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %> <%= svg_icon('loader', class: 'w-4 h-4 animate-spin disabled') %>
<% end %> <% end %>

@ -3,10 +3,10 @@
<div class="alert"> <div class="alert">
<%= svg_icon('x_circle', class: 'stroke-current shrink-0 h-6 w-6') %> <%= svg_icon('x_circle', class: 'stroke-current shrink-0 h-6 w-6') %>
<div> <div>
<h3 class="font-bold"> <p class="font-bold">
<%= I18n.t('errors.messages.not_saved', <%= I18n.t('errors.messages.not_saved',
count: resource.errors.count, count: resource.errors.count,
resource: resource.class.model_name.human.downcase) %></h3> resource: resource.class.model_name.human.downcase) %></p>
<% resource.errors.full_messages.each do |message| %> <% resource.errors.full_messages.each do |message| %>
<p class="text-sm"><%= message %></p> <p class="text-sm"><%= message %></p>
<% end %> <% end %>

@ -15,7 +15,7 @@
</div> </div>
<% end %> <% end %>
<file-dropzone data-name="verify_attachments" data-submit-on-upload="true" class="w-full"> <file-dropzone data-name="verify_attachments" data-submit-on-upload="true" class="w-full">
<label for="file" class="w-full block h-32 relative bg-base-300 hover:bg-base-200 rounded-md border border-base-content border-dashed"> <label for="file" class="w-full block h-32 relative bg-base-200 hover:bg-base-200/70 rounded-md border border-base-content border-dashed">
<div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center"> <div class="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<span data-target="file-dropzone.icon"> <span data-target="file-dropzone.icon">

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 15l6 -6"></path>
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464"></path>
<path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463"></path>
</svg>

After

Width:  |  Height:  |  Size: 496 B

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"></path>
<path d="M6 21v-2a4 4 0 0 1 4 -4h4c.348 0 .686 .045 1.008 .128"></path>
<text x="90%" y="95%" stroke-width="1" text-anchor="end" font-size="9" fill="black"><%= number %></text>
</svg>

After

Width:  |  Height:  |  Size: 523 B

@ -18,7 +18,7 @@
<div class="card-body"> <div class="card-body">
<div class="text-center transition-all"> <div class="text-center transition-all">
<div class="inline-block p-4 mb-6 -mt-16 bg-base-content rounded-full"> <div class="inline-block p-4 mb-6 -mt-16 bg-base-content rounded-full">
<%= svg_icon('brand_docker', class: 'w-10 h-10 text-base-100') %> <%= svg_icon('brand_docker', class: 'w-10 h-10 text-base-100 stroke-1') %>
</div> </div>
<h3 class="mb-4 text-2xl font-semibold">Easy to Install</h3> <h3 class="mb-4 text-2xl font-semibold">Easy to Install</h3>
<p class="text-base text-gray-500"> <p class="text-base text-gray-500">

@ -30,7 +30,7 @@
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %> <%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
</div> </div>
<% end %> <% end %>
<h2 class="text-2xl font-bold mt-8 mb-4">Change Password</h2> <p class="text-2xl font-bold mt-8 mb-4">Change Password</p>
<%= form_for current_user, url: update_password_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> <%= form_for current_user, url: update_password_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<div class="form-control"> <div class="form-control">
<%= f.label :password, 'New password', class: 'label' %> <%= f.label :password, 'New password', class: 'label' %>
@ -44,7 +44,7 @@
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %> <%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
</div> </div>
<% end %> <% end %>
<h2 class="text-2xl font-bold mt-8 mb-4">App URL</h2> <p class="text-2xl font-bold mt-8 mb-4">App URL</p>
<%= form_for @encrypted_config, url: update_app_url_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> <%= form_for @encrypted_config, url: update_app_url_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<div class="form-control"> <div class="form-control">
<%= f.text_field :value, required: true, class: 'base-input' %> <%= f.text_field :value, required: true, class: 'base-input' %>

@ -0,0 +1,17 @@
<clipboard-copy data-text="<%= text %>">
<label class="<%= local_assigns[:class] %>">
<input type="radio" class="peer hidden">
<span class="peer-checked:hidden flex items-center space-x-2">
<%= svg_icon('link', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
<span>
<%= local_assigns[:copy_title] || 'Copy' %>
</span>
</span>
<span class="hidden peer-checked:flex items-center space-x-2">
<%= svg_icon('clipboard_copy', class: local_assigns[:icon_class] || 'w-6 h-6 text-white') %>
<span>
<%= local_assigns[:copied_title] || 'Copied' %>
</span>
</span>
</label>
</clipboard-copy>

@ -7,7 +7,7 @@
<div class="space-x-6"> <div class="space-x-6">
<%= link_to 'Settings', settings_profile_index_path, class: 'font-medium text-lg' %> <%= link_to 'Settings', settings_profile_index_path, class: 'font-medium text-lg' %>
<div class="dropdown dropdown-end z-50"> <div class="dropdown dropdown-end z-50">
<label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full w-8 p-2"> <label tabindex="0" class="cursor-pointer bg-base-content text-purple-300 rounded-full p-2 w-9 justify-center flex">
<span class="text-sm align-text-top"><%= current_user.initials %></span> <span class="text-sm align-text-top"><%= current_user.initials %></span>
</label> </label>
<ul tabindex="0" class="dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box whitespace-nowrap"> <ul tabindex="0" class="dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box whitespace-nowrap">

@ -1,7 +1,7 @@
<div class="card bg-base-200"> <div class="card bg-base-200">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="max-w-md mx-auto"> <div class="max-w-md mx-auto">
<h1 class="text-4xl font-semibold text-gray-700 mb-4">Nothing to display</h1> <p class="text-4xl font-semibold text-gray-700 mb-4">Nothing to display</p>
<p class="text-gray-500">We apologize, but currently there is no data available to be displayed. You can try adding new entries or refreshing the page to see if any data becomes available.</p> <p class="text-gray-500">We apologize, but currently there is no data available to be displayed. You can try adding new entries or refreshing the page to see if any data becomes available.</p>
</div> </div>
</div> </div>

@ -3,15 +3,13 @@
<div class="card-body"> <div class="card-body">
<div class="space-y-4 w-full md:px-10 mx-auto"> <div class="space-y-4 w-full md:px-10 mx-auto">
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center justify-center"> <a href="/" class="flex justify-center">
<div class="flex items-center"> <span class="mr-3">
<div class="mr-3">
<%= render 'shared/logo', width: '50px', height: '50px' %> <%= render 'shared/logo', width: '50px', height: '50px' %>
</div> </span>
<h1 class="text-5xl font-bold text-center">DocuSeal</h1> <h1 class="text-5xl font-bold text-center">DocuSeal</h1>
</div> </a>
</div> <p class="text-xl font-semibold text-center">You have been invited to submit the form</p>
<h2 class="text-xl font-semibold text-center">You have been invited to submit the form</h2>
<div class="flex items-center bg-neutral rounded-xl p-4"> <div class="flex items-center bg-neutral rounded-xl p-4">
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-3"> <div class="mr-3">
@ -23,7 +21,7 @@
</svg> </svg>
</div> </div>
<div> <div>
<h3 class="text-gray-100 font-bold mb-1"><%= @template.name %></h3> <p class="text-gray-100 font-bold mb-1"><%= @template.name %></p>
<p class="text-sm text-gray-300">Invited by <%= @template.account.name %></p> <p class="text-sm text-gray-300">Invited by <%= @template.account.name %></p>
</div> </div>
</div> </div>

@ -4,7 +4,7 @@
<div class="alert my-4"> <div class="alert my-4">
<%= svg_icon('info_circle', class: 'stroke-current flex-shrink-0 w-6 h-6') %> <%= svg_icon('info_circle', class: 'stroke-current flex-shrink-0 w-6 h-6') %>
<div> <div>
<h3 class="font-bold">Store all files on disk</h3> <p class="font-bold">Store all files on disk</p>
<p class="text-gray-700"> <p class="text-gray-700">
No configs are needed but make sure your disk is persistent No configs are needed but make sure your disk is persistent
<br> <br>

@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem"> <a href="#" class="btn btn-primary btn-sm w-full flex items-center justify-center" data-action="click:dynamic-list#addItem">
<%= svg_icon('user_plus', class: 'w-4 h-4') %> <%= svg_icon('user_plus', class: 'w-4 h-4 stroke-2') %>
<span>Add Recipient</span> <span>Add Recipient</span>
</a> </a>
</dynamic-list> </dynamic-list>
@ -45,7 +45,7 @@
<div class="alert my-4"> <div class="alert my-4">
<%= svg_icon('info_circle', class: 'w-6 h-6') %> <%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div> <div>
<h3 class="font-bold">SMTP not Configured</h3> <p class="font-bold">SMTP not Configured</p>
<p class="text-gray-700"> <p class="text-gray-700">
Configure SMTP settings in order to send emails: Configure SMTP settings in order to send emails:
<br> <br>

@ -20,7 +20,7 @@
</svg> </svg>
</div> </div>
<div> <div>
<h3 class="text-lg text-gray-700 font-bold mb-1"><%= @submitter.submission.template.name %></h3> <p class="text-lg text-gray-700 font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-sm text-gray-500">Signed on <%= l(@submitter.completed_at.to_date, format: :long) %></p> <p class="text-sm text-gray-500">Signed on <%= l(@submitter.completed_at.to_date, format: :long) %></p>
</div> </div>
</div> </div>

@ -1,15 +1,14 @@
<div class="card card-compact bg-primary mb-12 md:card-normal"> <div class="flex md:justify-between items-start mb-6">
<div class="card-body"> <h1 class="text-4xl font-semibold mr-4" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
<div class="grid md:grid-cols-2 gap-4 md:flex md:justify-between">
<h2 class="card-title text-4xl ">
<%= @template.name %> <%= @template.name %>
</h2> </h1>
<div class="flex md:justify-between space-x-2"> <div class="flex md:justify-between space-x-2 flex-none">
<%= link_to new_template_path(base_template_id: @template.id), class: 'btn btn-outline btn-sm', data: { turbo_frame: :modal } do %> <%= button_to button_title(title: 'Remove', icon: svg_icon('trash', class: 'w-6 h-6')), template_path(@template), class: 'btn btn-outline', method: :delete, data: { turbo_confirm: 'Are you sure?' } %>
<%= link_to new_template_path(base_template_id: @template.id), class: 'btn btn-outline', data: { turbo_frame: :modal } do %>
<%= svg_icon('copy', class: 'w-6 h-6') %> <%= svg_icon('copy', class: 'w-6 h-6') %>
<span>Clone</span> <span>Clone</span>
<% end %> <% end %>
<%= link_to edit_template_path(@template), class: 'btn btn-outline btn-sm' do %> <%= link_to edit_template_path(@template), class: 'btn btn-outline' do %>
<span class="flex items-center justify-center space-x-2"> <span class="flex items-center justify-center space-x-2">
<%= svg_icon('pencil', class: 'w-6 h-6') %> <%= svg_icon('pencil', class: 'w-6 h-6') %>
<span>Edit</span> <span>Edit</span>
@ -17,87 +16,108 @@
<% end %> <% end %>
</div> </div>
</div> </div>
<% if @template.submitters.size == 1 %> <div class="flex justify-between mb-4 items-end">
<div class="join w-full"> <p class="text-3xl font-bold">Submissions</p>
<buttun class="btn bg-neutral btn-disabled text-white join-item"> <div class="flex space-x-2">
Share link <% if @template.submitters.to_a.size == 1 %>
</buttun> <%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %>
<input id="share-link-input" autocomplete="off" type="text" class="input input-bordered w-full join-item" value="<%= start_form_url(slug: @template.slug) %>" disabled>
<clipboard-copy class="btn btn-neutral btn-square join-item text-white font-bold swap swap-active" for="share-link-input">
<%= svg_icon('clipboard', class: 'w-6 h-6 swap-on text-white') %>
<%= svg_icon('clipboard_copy', class: 'w-6 h-6 swap-off text-white') %>
</clipboard-copy>
</div>
<% end %> <% end %>
</div> <%= link_to new_template_submission_path(@template), class: 'btn btn-primary text-base', data: { turbo_frame: 'modal' } do %>
</div> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<div class="flex justify-between mb-4">
<h1 class="text-3xl font-bold">Recipients</h1>
<%= link_to new_template_submission_path(@template), class: 'btn btn-primary btn-sm gap-2', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('plus', class: 'w-6 h-6') %>
<span class="hidden md:block">Add Recipients</span> <span class="hidden md:block">Add Recipients</span>
<% end %> <% end %>
</div> </div>
<div class="overflow-x-auto"> </div>
<% if @submissions.any? %> <% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
<table class="table w-full table-lg rounded-t-2xl overflow-hidden"> <% if @submissions.present? %>
<thead class="bg-base-200"> <div class="space-y-4">
<tr class="text-neutral uppercase">
<th>
Email
</th>
<th>
Status
</th>
<th>
Share Link
</th>
<th class="text-right" width="1px">
</th>
</tr>
</thead>
<tbody>
<% @submissions.each do |submission| %> <% @submissions.each do |submission| %>
<tr> <a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex justify-between rounded-2xl px-6 py-5 items-center">
<td> <% if submission.template.submitters.size == 1 %>
<% submission.submitters.each do |submitter| %> <div>
<% submitter = submission.submitters.first %>
<div class="flex items-center space-x-4">
<span class="flex items-center space-x-2">
<%= svg_icon('user', class: 'w-6 h-6 stroke-2') %>
<span class="text-lg">
<%= submitter.email %> <%= submitter.email %>
<br> </span>
</span>
</div>
</div>
<div class="flex space-x-2 items-center">
<span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1">
<%= submitter.status %>
</span>
<% if submitter.completed_at? %>
<form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(submitter.slug) %>" class="btn btn-sm btn-neutral text-white w-36">
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span>Download</span>
</span>
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span>Downloa...</span>
</span>
</download-button>
</button>
</form>
<% else %>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submission.submitters.first.slug), class: 'btn btn-sm btn-neutral text-white w-36', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link' %>
<% end %> <% end %>
</td> <%= button_to submitter.completed_at? ? 'Archive' : 'Remove', submission_path(submission), class: 'btn btn-outline btn-sm w-28', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
<td> </div>
<% submission.submitters.each do |submitter| %> <% else %>
<div> <div class="space-y-1 w-full mr-4">
<span class="badge badge-info badge-outline"> <% submission.template.submitters.each_with_index do |item, index| %>
<% submitter = submission.submitters.find { |e| e.uuid == item['uuid'] } %>
<div class="flex justify-between items-center">
<span class="flex items-center space-x-2 text-lg">
<%= render 'icons/user_number', class: 'w-6 h-6 stroke-2', number: index + 1 %>
<span>
<%= submitter.email %>
</span>
</span>
<% unless submission.submitters.all?(&:completed_at?) %>
<div class="flex items-center space-x-3">
<span class="badge w-24 <%= status_badges[submitter.status] %> btn-xs uppercase text-xs font-medium border-1">
<%= submitter.status %> <%= submitter.status %>
</span> </span>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-xs text-xs btn-neutral text-white w-32', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link' %>
</div>
<% end %>
</div> </div>
<% end %> <% end %>
</td>
<td>
<% submission.submitters.each do |submitter| %>
<% share_link_input_id = "share-link-input_#{submitter.id}" %>
<div class="join ">
<input id="<%= share_link_input_id %>" autocomplete="off" type="text" class="input input-xs input-bordered join-item" value="<%= submit_form_url(slug: submitter.slug) %>" disabled>
<clipboard-copy class="btn btn-xs btn-neutral btn-square join-item text-white font-bold swap swap-active" for="<%= share_link_input_id %>">
<%= svg_icon('clipboard', class: 'w-3 h-3 swap-on text-white') %>
<%= svg_icon('clipboard_copy', class: 'w-3 h-3 swap-off text-white') %>
</clipboard-copy>
</div> </div>
<br> <div class="flex space-x-2 items-center">
<% if submission.submitters.all?(&:completed_at?) %>
<span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1">
<%= submitter.status %>
</span>
<form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()">
<download-button data-src="<%= submitter_download_index_path(submission.submitters.select(&:completed_at?).max_by(&:completed_at).slug) %>" class="btn btn-sm btn-neutral text-white w-36">
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
<%= svg_icon('download', class: 'w-5 h-5 stroke-2') %>
<span>Download</span>
</span>
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin') %>
<span>Downloa...</span>
</span>
</download-button>
</button>
</form>
<% end %> <% end %>
</td> <%= button_to submitter.completed_at? ? 'Archive' : 'Remove', submission_path(submission), class: 'btn btn-outline btn-sm w-28', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' }, onclick: 'event.stopPropagation()' %>
<td class="flex items-center space-x-2 justify-end"> </div>
<%= link_to 'View', submission_path(submission), title: 'View', class: 'btn btn-outline btn-xs' %> <% end %>
<%= button_to 'Remove', submission_path(submission), class: 'btn btn-outline btn-error btn-xs', title: 'Delete', method: :delete, data: { turbo_confirm: 'Are you sure?' } %> </a>
</td>
</tr>
<% end %> <% end %>
</tbody>
</table>
</div> </div>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'Submissions' %> <%= render 'shared/pagination', pagy: @pagy, items_name: 'Submissions' %>
<% else %> <% else %>
<%= render 'shared/no_data_banner' %> <%= render 'shared/no_data_banner' %>
<% end %> <% end %>
</div>

@ -38,7 +38,7 @@
<%= user.email %> <%= user.email %>
</td> </td>
<td> <td>
<span class="badge badge-success badge-outline"> <span class="badge badge-info badge-outline">
<%= user.role %> <%= user.role %>
</span> </span>
</td> </td>

@ -4,6 +4,7 @@ class CreateSubmissions < ActiveRecord::Migration[7.0]
def change def change
create_table :submissions do |t| create_table :submissions do |t|
t.references :template, null: false, foreign_key: true, index: true t.references :template, null: false, foreign_key: true, index: true
t.references :created_by_user, null: true, foreign_key: { to_table: :users }, index: true
t.datetime :deleted_at t.datetime :deleted_at

@ -62,9 +62,11 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do
create_table "submissions", force: :cascade do |t| create_table "submissions", force: :cascade do |t|
t.bigint "template_id", null: false t.bigint "template_id", null: false
t.bigint "created_by_user_id"
t.datetime "deleted_at" t.datetime "deleted_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["template_id"], name: "index_submissions_on_template_id" t.index ["template_id"], name: "index_submissions_on_template_id"
end end
@ -133,6 +135,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do
add_foreign_key "active_storage_variant_records", "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 "encrypted_configs", "accounts"
add_foreign_key "submissions", "templates" add_foreign_key "submissions", "templates"
add_foreign_key "submissions", "users", column: "created_by_user_id"
add_foreign_key "submitters", "submissions" add_foreign_key "submitters", "submissions"
add_foreign_key "templates", "accounts" add_foreign_key "templates", "accounts"
add_foreign_key "templates", "users", column: "author_id" add_foreign_key "templates", "users", column: "author_id"

@ -47,15 +47,7 @@ module Submissions
when 'image', 'signature' when 'image', 'signature'
attachment = submitter.attachments.find { |a| a.uuid == value } attachment = submitter.attachments.find { |a| a.uuid == value }
image_data = io = StringIO.new(download_supported_image_data(attachment))
if SUPPORTED_IMAGE_TYPES.include?(attachment.content_type)
attachment.download
else
Vips::Image.new_from_buffer(attachment.download, '')
.write_to_buffer('.png')
end
io = StringIO.new(image_data)
scale = [(area['w'] * width) / attachment.metadata['width'], scale = [(area['w'] * width) / attachment.metadata['width'],
(area['h'] * height) / attachment.metadata['height']].min (area['h'] * height) / attachment.metadata['height']].min
@ -249,7 +241,7 @@ module Submissions
page.box.height = attachment.metadata['height'] * scale page.box.height = attachment.metadata['height'] * scale
page.canvas.image( page.canvas.image(
StringIO.new(attachment.download), StringIO.new(download_supported_image_data(attachment)),
at: [0, 0], at: [0, 0],
width: page.box.width, width: page.box.width,
height: page.box.height height: page.box.height
@ -257,5 +249,14 @@ module Submissions
pdf pdf
end end
def download_supported_image_data(attachment)
if SUPPORTED_IMAGE_TYPES.include?(attachment.content_type)
attachment.download
else
Vips::Image.new_from_buffer(attachment.download, '')
.write_to_buffer('.png')
end
end
end end
end end

@ -7,6 +7,7 @@ module.exports = {
'./app/views/submit_form/**/*.erb', './app/views/submit_form/**/*.erb',
'./app/views/start_form/**/*.erb', './app/views/start_form/**/*.erb',
'./app/views/shared/_button_title.html.erb', './app/views/shared/_button_title.html.erb',
'./app/views/shared/_attribution.html.erb',
'./app/views/send_submission_copy/**/*.erb' './app/views/send_submission_copy/**/*.erb'
] ]
} }

Loading…
Cancel
Save