adjust templates, add timezone and locale

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

@ -21,6 +21,7 @@ gem 'pg'
gem 'premailer-rails' gem 'premailer-rails'
gem 'puma' gem 'puma'
gem 'rails' gem 'rails'
gem 'rails-i18n'
gem 'ruby-vips' gem 'ruby-vips'
gem 'shakapacker' gem 'shakapacker'
gem 'sqlite3' gem 'sqlite3'

@ -352,6 +352,9 @@ GEM
rails-html-sanitizer (1.6.0) rails-html-sanitizer (1.6.0)
loofah (~> 2.21) loofah (~> 2.21)
nokogiri (~> 1.14) nokogiri (~> 1.14)
rails-i18n (7.0.7)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.0.5) railties (7.0.5)
actionpack (= 7.0.5) actionpack (= 7.0.5)
activesupport (= 7.0.5) activesupport (= 7.0.5)
@ -511,6 +514,7 @@ DEPENDENCIES
pry-rails pry-rails
puma puma
rails rails
rails-i18n
rspec-rails rspec-rails
rubocop rubocop
rubocop-performance rubocop-performance

@ -0,0 +1,37 @@
# frozen_string_literal: true
class AccountsController < ApplicationController
LOCALE_OPTIONS = {
'en-US' => 'English (United States)',
'en-GB' => 'English (United Kingdom)',
'es-ES' => 'Spanish (Spain)',
'pt-PT' => 'Portuguese (Portugal)',
'de-DE' => 'German (Germany)'
}.freeze
def show; end
def update
current_account.update!(account_params)
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
key: EncryptedConfig::APP_URL_KEY)
@encrypted_config.update!(app_url_params)
Docuseal.refresh_default_url_options!
redirect_to settings_account_path, notice: 'Account information has been updated'
rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity
end
private
def account_params
params.require(:account).permit(:name, :timezone, :locale)
end
def app_url_params
params.require(:encrypted_config).permit(:value)
end
end

@ -1,13 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class ProfileController < ApplicationController class ProfileController < ApplicationController
before_action :load_encrypted_config, only: %i[index update_app_url]
def index; end def index; end
def update_contact def update_contact
if current_user.update(contact_params) if current_user.update(contact_params)
redirect_to settings_profile_index_path, notice: 'Contact information successfully updated' redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_entity
end end
@ -16,17 +14,7 @@ class ProfileController < ApplicationController
def update_password def update_password
if current_user.update(password_params) if current_user.update(password_params)
bypass_sign_in(current_user) bypass_sign_in(current_user)
redirect_to settings_profile_index_path, notice: 'Password successfully changed' redirect_to settings_profile_index_path, notice: 'Password has been changed'
else
render :index, status: :unprocessable_entity
end
end
def update_app_url
if @encrypted_config.update(app_url_params)
Docuseal.refresh_default_url_options!
redirect_to settings_profile_index_path, notice: 'App URL successfully changed'
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_entity
end end
@ -34,20 +22,11 @@ class ProfileController < ApplicationController
private private
def load_encrypted_config
@encrypted_config =
EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY)
end
def contact_params def contact_params
params.require(:user).permit(:first_name, :last_name, :email, account_attributes: %i[name]) params.require(:user).permit(:first_name, :last_name, :email)
end end
def password_params def password_params
params.require(:user).permit(:password, :password_confirmation) params.require(:user).permit(:password, :password_confirmation)
end end
def app_url_params
params.require(:encrypted_config).permit(:value)
end
end end

@ -5,6 +5,7 @@ class RegistrationsController < Devise::RegistrationsController
def build_resource(_hash = {}) def build_resource(_hash = {})
account = Account.new(account_params) account = Account.new(account_params)
account.timezone = Accounts.normalize_timezone(account.timezone)
self.resource = account.users.new(user_params) self.resource = account.users.new(user_params)
end end
@ -18,6 +19,6 @@ class RegistrationsController < Devise::RegistrationsController
def account_params def account_params
return {} if params[:account].blank? return {} if params[:account].blank?
params.require(:account).permit(:name) params.require(:account).permit(:name, :timezone)
end end
end end

@ -15,6 +15,8 @@ class SetupController < ApplicationController
def create def create
@account = Account.new(account_params) @account = Account.new(account_params)
@account.timezone = Accounts.normalize_timezone(@account.timezone)
@user = @account.users.new(user_params) @user = @account.users.new(user_params)
if @user.save if @user.save
@ -24,6 +26,8 @@ class SetupController < ApplicationController
] ]
@account.encrypted_configs.create!(encrypted_configs) @account.encrypted_configs.create!(encrypted_configs)
Docuseal.refresh_default_url_options!
sign_in(@user) sign_in(@user)
redirect_to root_path redirect_to root_path
@ -43,7 +47,7 @@ class SetupController < ApplicationController
def account_params def account_params
return {} unless params[:account] return {} unless params[:account]
params.require(:account).permit(:name) params.require(:account).permit(:name, :timezone)
end end
def encrypted_config_params def encrypted_config_params

@ -16,7 +16,7 @@ class StartFormController < ApplicationController
.find_or_initialize_by(**submitter_params) .find_or_initialize_by(**submitter_params)
if @submitter.completed_at? if @submitter.completed_at?
redirect_to start_form_completed_path(@template.slug, email: submission_params[:email]) redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
else else
@submitter.assign_attributes( @submitter.assign_attributes(
uuid: @template.submitters.first['uuid'], uuid: @template.submitters.first['uuid'],
@ -36,7 +36,7 @@ class StartFormController < ApplicationController
end end
def completed def completed
@submitter = Submitter.where(submission: @template.submitters).find_by(email: params[:email]) @submitter = Submitter.where(submission: @template.submissions).find_by!(email: params[:email])
end end
private private
@ -46,7 +46,7 @@ class StartFormController < ApplicationController
end end
def load_template def load_template
slug = params[:slug] || params[:start_template_slug] slug = params[:slug] || params[:start_form_slug]
@template = Template.find_by!(slug:) @template = Template.find_by!(slug:)
end end

@ -19,6 +19,7 @@ class SubmitFormController < ApplicationController
submitter = Submitter.find_by!(slug: params[:slug]) submitter = Submitter.find_by!(slug: params[:slug])
submitter.values.merge!(normalized_values) submitter.values.merge!(normalized_values)
submitter.completed_at = Time.current if params[:completed] == 'true' submitter.completed_at = Time.current if params[:completed] == 'true'
submitter.opened_at ||= Time.current
submitter.save! submitter.save!

@ -12,6 +12,7 @@ import ClipboardCopy from './elements/clipboard_copy'
import DynamicList from './elements/dynamic_list' import DynamicList from './elements/dynamic_list'
import DownloadButton from './elements/download_button' import DownloadButton from './elements/download_button'
import SetOriginUrl from './elements/set_origin_url' import SetOriginUrl from './elements/set_origin_url'
import SetTimezone from './elements/set_timezone'
document.addEventListener('turbo:before-cache', () => { document.addEventListener('turbo:before-cache', () => {
window.flash?.remove() window.flash?.remove()
@ -32,6 +33,7 @@ window.customElements.define('clipboard-copy', ClipboardCopy)
window.customElements.define('dynamic-list', DynamicList) window.customElements.define('dynamic-list', DynamicList)
window.customElements.define('download-button', DownloadButton) window.customElements.define('download-button', DownloadButton)
window.customElements.define('set-origin-url', SetOriginUrl) window.customElements.define('set-origin-url', SetOriginUrl)
window.customElements.define('set-timezone', SetTimezone)
window.customElements.define('template-builder', class extends HTMLElement { window.customElements.define('template-builder', class extends HTMLElement {
connectedCallback () { connectedCallback () {

@ -0,0 +1,7 @@
export default class extends HTMLElement {
connectedCallback () {
if (this.dataset.inputId) {
document.getElementById(this.dataset.inputId).value = Intl.DateTimeFormat().resolvedOptions().timeZone
}
}
}

@ -207,7 +207,7 @@ export default {
}, },
formattedDate () { formattedDate () {
if (this.field.type === 'date' && this.modelValue) { if (this.field.type === 'date' && this.modelValue) {
return new Intl.DateTimeFormat({ year: 'numeric', month: 'numeric', day: 'numeric' }).format(new Date(this.modelValue)) return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(this.modelValue))
} else { } else {
return '' return ''
} }

@ -41,6 +41,10 @@
>{{ currentField.name }} >{{ currentField.name }}
<template v-if="!currentField.required">(optional)</template> <template v-if="!currentField.required">(optional)</template>
</label> </label>
<div
v-else
class="py-1"
/>
<div> <div>
<input <input
:id="currentField.uuid" :id="currentField.uuid"
@ -62,6 +66,10 @@
>{{ currentField.name }} >{{ currentField.name }}
<template v-if="!currentField.required">(optional)</template> <template v-if="!currentField.required">(optional)</template>
</label> </label>
<div
v-else
class="py-1"
/>
<div class="text-center"> <div class="text-center">
<input <input
:id="currentField.uuid" :id="currentField.uuid"
@ -82,6 +90,10 @@
>{{ currentField.name }} >{{ currentField.name }}
<template v-if="!currentField.required">(optional)</template> <template v-if="!currentField.required">(optional)</template>
</label> </label>
<div
v-else
class="py-1"
/>
<select <select
:id="currentField.uuid" :id="currentField.uuid"
:required="currentField.required" :required="currentField.required"
@ -246,7 +258,7 @@
:key="step[0].uuid" :key="step[0].uuid"
href="#" href="#"
class="inline border border-base-300 h-3 w-3 rounded-full mx-1" class="inline border border-base-300 h-3 w-3 rounded-full mx-1"
:class="{ 'bg-base-200': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }" :class="{ 'bg-base-300': index === currentStep, 'bg-base-content': index < currentStep || isCompleted, 'bg-white': index > currentStep }"
@click.prevent="isCompleted ? '' : goToStep(step, true)" @click.prevent="isCompleted ? '' : goToStep(step, true)"
/> />
</div> </div>

@ -5,7 +5,9 @@
# Table name: accounts # Table name: accounts
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# locale :string not null
# name :string not null # name :string not null
# timezone :string not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# #
@ -15,4 +17,7 @@ class Account < ApplicationRecord
has_many :templates, dependent: :destroy has_many :templates, dependent: :destroy
has_many :active_users, -> { active }, dependent: :destroy, has_many :active_users, -> { active }, dependent: :destroy,
inverse_of: :account, class_name: 'User' inverse_of: :account, class_name: 'User'
attribute :timezone, :string, default: 'UTC'
attribute :locale, :string, default: 'en-US'
end end

@ -50,4 +50,8 @@ class Submitter < ApplicationRecord
'awaiting' 'awaiting'
end end
end end
def status_event_at
completed_at || opened_at || sent_at || created_at
end
end end

@ -52,8 +52,6 @@ class User < ApplicationRecord
scope :active, -> { where(deleted_at: nil) } scope :active, -> { where(deleted_at: nil) }
accepts_nested_attributes_for :account, update_only: true
def active_for_authentication? def active_for_authentication?
!deleted_at? !deleted_at?
end end

@ -0,0 +1,36 @@
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
<%= render 'shared/settings_nav' %>
<div class="flex-grow max-w-xl mx-auto">
<h1 class="text-4xl font-bold mb-4">Account</h1>
<%= form_for '', url: settings_account_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<%= f.fields_for current_account do |ff| %>
<div class="form-control">
<%= ff.label :name, 'Company Name', class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input' %>
</div>
<div class="grid md:grid-cols-2 gap-4">
<div class="form-control">
<%= ff.label :timezone, class: 'label' %>
<%= ff.select :timezone, nil, {}, class: 'select base-input w-full font-normal' do %>
<%= time_zone_options_for_select(current_account.timezone) %>
<% end %>
</div>
<div class="form-control">
<%= ff.label :locale, 'Time format', class: 'label' %>
<%= ff.select :locale, options_for_select(controller.class::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'select base-input w-full font-normal' %>
</div>
</div>
<% end %>
<%= f.fields_for @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) do |ff| %>
<div class="form-control">
<%= ff.label :value, 'App URL', class: 'label' %>
<%= ff.text_field :value, autocomplete: 'off', class: 'base-input' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
</div>
<% end %>
</div>
<div class="w-0 md:w-52"></div>
</div>

@ -20,7 +20,7 @@
</p> </p>
<p class="flex items-center space-x-1 text-xs text-base-content/60"> <p class="flex items-center space-x-1 text-xs text-base-content/60">
<%= svg_icon('calendar', class: 'w-4 h-4') %> <%= svg_icon('calendar', class: 'w-4 h-4') %>
<span><%= l(template.created_at, format: :long) %></span> <span><%= l(template.created_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %></span>
</p> </p>
</div> </div>
</a> </a>
@ -39,20 +39,24 @@
</div> </div>
<% end %> <% end %>
</div> </div>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'Templates' %> <%= render 'shared/pagination', pagy: @pagy, items_name: 'templates' %>
<% else %> <% else %>
<div class="card bg-base-200 h-96 mb-2"> <div class="card bg-base-200 h-96 mb-2">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="flex items-center h-full"> <div class="flex items-center h-full">
<div class="mx-auto">
<div class="max-w-xl mx-auto">
<h1 class="text-5xl font-bold text-base-content">👋 Welcome to DocuSeal</h1>
</div>
<div class="max-w-lg mx-auto"> <div class="max-w-lg mx-auto">
<h1 class="text-5xl font-bold">Welcome to DocuSeal</h1> <p class="py-6 text-gray-600">Streamline document workflows, from creating customizable templates to filling and signing document forms, with DocuSeal</p>
<p class="py-6">Streamline document workflows, from creating customizable templates to filling and signing document forms, with DocuSeal</p> <%= link_to new_template_path, class: 'base-button', data: { turbo_frame: :modal } do %>
<%= link_to new_template_path, class: 'btn btn-neutral', data: { turbo_frame: :modal } do %> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<%= svg_icon('plus', class: 'w-6 h-6') %>
<span>Create Template</span> <span>Create Template</span>
<% end %> <% end %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<% end %> <% end %>

@ -4,6 +4,7 @@
<div class="space-y-2"> <div class="space-y-2">
<%= render 'devise/shared/error_messages', resource: %> <%= render 'devise/shared/error_messages', resource: %>
<%= f.fields_for resource do |ff| %> <%= f.fields_for resource do |ff| %>
<div class="grid gap-2 md:grid-cols-2 md:gap-4">
<div class="form-control"> <div class="form-control">
<%= ff.label :first_name, class: 'label' %> <%= ff.label :first_name, class: 'label' %>
<%= ff.text_field :first_name, required: true, class: 'base-input' %> <%= ff.text_field :first_name, required: true, class: 'base-input' %>
@ -12,6 +13,13 @@
<%= ff.label :last_name, class: 'label' %> <%= ff.label :last_name, class: 'label' %>
<%= ff.text_field :last_name, required: true, class: 'base-input' %> <%= ff.text_field :last_name, required: true, class: 'base-input' %>
</div> </div>
</div>
<% end %>
<%= f.fields_for resource do |ff| %>
<div class="form-control">
<%= ff.label :email, class: 'label' %>
<%= ff.email_field :email, required: true, class: 'base-input' %>
</div>
<% end %> <% end %>
<%= f.fields_for resource.account do |ff| %> <%= f.fields_for resource.account do |ff| %>
<div class="form-control"> <div class="form-control">
@ -20,10 +28,6 @@
</div> </div>
<% end %> <% end %>
<%= f.fields_for resource do |ff| %> <%= f.fields_for resource do |ff| %>
<div class="form-control">
<%= ff.label :email, class: 'label' %>
<%= ff.email_field :email, required: true, class: 'base-input' %>
</div>
<div class="form-control"> <div class="form-control">
<%= ff.label :password, class: 'label' %> <%= ff.label :password, class: 'label' %>
<%= ff.password_field :password, required: true, class: 'base-input' %> <%= ff.password_field :password, required: true, class: 'base-input' %>

@ -2,9 +2,6 @@
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow max-w-xl mx-auto"> <div class="flex-grow max-w-xl mx-auto">
<h1 class="text-4xl font-bold mb-4">Email SMTP</h1> <h1 class="text-4xl font-bold mb-4">Email SMTP</h1>
<p>
Configure your to send emails (TODO)
</p>
<% value = @encrypted_config.value || {} %> <% value = @encrypted_config.value || {} %>
<%= form_for @encrypted_config, url: settings_email_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> <%= form_for @encrypted_config, url: settings_email_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<%= f.fields_for :value do |ff| %> <%= f.fields_for :value do |ff| %>
@ -29,11 +26,11 @@
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control">
<%= ff.label :from_email, 'Send from', class: 'label' %> <%= ff.label :from_email, 'Send from Email', class: 'label' %>
<%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %> <%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %>
</div> </div>
<% end %> <% end %>
<div class="form-control"> <div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %> <%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
</div> </div>
<% end %> <% end %>

@ -50,7 +50,7 @@
</div> </div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<%= svg_icon('calendar', class: 'w-5 h-5 inline') %> <%= svg_icon('calendar', class: 'w-5 h-5 inline') %>
<span><%= l(signature.signing_time, format: :long) %></span> <span><%= l(signature.signing_time.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %></span>
</div> </div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<%= svg_icon('certificate', class: 'w-5 h-5 inline') %> <%= svg_icon('certificate', class: 'w-5 h-5 inline') %>

@ -1,7 +1,7 @@
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0"> <div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow max-w-xl mx-auto"> <div class="flex-grow max-w-xl mx-auto">
<h1 class="text-4xl font-bold mb-4">Verify PDF Signature</h1> <h1 class="text-4xl font-bold mb-4">PDF Signature</h1>
<div id="result"> <div id="result">
<p class="mb-2"> <p class="mb-2">
Upload signed PDF file to validate its signature: Upload signed PDF file to validate its signature:

@ -1,5 +1,5 @@
<div class="max-w-xl mx-auto px-2"> <div class="max-w-xl mx-auto px-2">
<h1 class="text-4xl font-bold text-center my-8">Welcome to Docuseal</h1> <h1 class="text-4xl font-bold text-center my-8">👋 Welcome to Docuseal</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'space-y-6' }) do |f| %> <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'space-y-6' }) do |f| %>
<div class="space-y-2"> <div class="space-y-2">
<%= render 'devise/shared/error_messages', resource: %> <%= render 'devise/shared/error_messages', resource: %>

@ -8,65 +8,61 @@
DocuSeal DocuSeal
</h1> </h1>
</div> </div>
<p class="mb-16 text-lg text-center text-gray-500"> <h2 class="mb-16 text-lg text-center text-gray-500">
A self-hosted, web-based platform providing secure and efficient digital document signing and transaction management. A self-hosted and open-source web platform that provides secure and efficient digital document signing and processing.
</p> </h2>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 gap-12 md:gap-10 md:grid-cols-2"> <div class="grid grid-cols-1 gap-12 md:gap-10 md:grid-cols-2">
<div class="card bg-base-300"> <div class="card bg-base-200">
<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-4 -mt-16 bg-base-content rounded-full">
<%= svg_icon('brand_docker', class: 'w-10 h-10 text-base-100 stroke-1') %> <%= 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 Start</h3>
<p class="text-base text-gray-500"> <p class="text-base text-gray-500">
Simply initiate the process on your platform, deploy the solution via <a href="https://hub.docker.com/r/docuseal/docuseal" class="link link-neutral font-bold" target="_blank">Docker</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>.
or opt for its packaged version for ease of use.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-300"> <div class="card bg-base-200">
<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-4 -mt-16 bg-base-content rounded-full">
<%= svg_icon('devices', class: 'w-10 h-10 text-base-100') %> <%= svg_icon('devices', class: 'w-10 h-10 text-base-100') %>
</div> </div>
<h3 class="mb-4 text-2xl font-semibold">Mobile Optimized</h3> <h3 class="mb-4 text-2xl font-semibold">Mobile Optimized</h3>
<p class="text-base text-gray-500"> <p class="text-base text-gray-500">
This self-hosted solution is mobile-ready, designed to offer a seamless user experience on any device. Review and sign digital documents online from any device.
Manage documents with ease, right from your smartphone or tablet. Docuseal document forms are optimized for screens of all sizes.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-300"> <div class="card bg-base-200">
<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-4 -mt-16 bg-base-content rounded-full">
<%= svg_icon('shield_check', class: 'w-10 h-10 text-base-100') %> <%= svg_icon('shield_check', class: 'w-10 h-10 text-base-100') %>
</div> </div>
<h3 class="mb-4 text-2xl font-semibold">Secure</h3> <h3 class="mb-4 text-2xl font-semibold">Secure</h3>
<p class="text-base text-gray-500"> <p class="text-base text-gray-500">
With a focus on security, this solution integrates top-level encryption within your own hosting infrastructure. Host it on your hardware under a VPN to ensure that important documents can be accesses only within your organization.
Comprehensive tracking of every action assures a secure environment for all digital transactions.
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-300"> <div class="card bg-base-200">
<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-4 -mt-16 bg-base-content rounded-full">
<%= svg_icon('brand_github', class: 'w-10 h-10 text-base-100') %> <%= svg_icon('brand_github', class: 'w-10 h-10 text-base-100') %>
</div> </div>
<h3 class="mb-4 text-2xl font-semibold">Open Source</h3> <h3 class="mb-4 text-2xl font-semibold">Open Source</h3>
<p class="text-base text-gray-500"> <p class="text-base text-gray-500">
Don't hesitate to fetch <a href="https://github.com/docusealhq" class="link link-neutral font-bold" target="_blank">github.com/docusealhq</a>. Source code is available under <a href="https://github.com/docusealhq/docuseal" class="link link-neutral font-bold" target="_blank">github.com/docusealhq</a>.<br>Open-source contributors are always ready to help!
We warmly invite you to participate and help us enhance this project.
There's no need to shy away from becoming a contributor!
</p> </p>
</div> </div>
</div> </div>

@ -2,9 +2,6 @@
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow max-w-xl mx-auto"> <div class="flex-grow max-w-xl mx-auto">
<h1 class="text-4xl font-bold mb-4">Profile</h1> <h1 class="text-4xl font-bold mb-4">Profile</h1>
<p>
Manage your contact information
</p>
<%= form_for current_user, url: update_contact_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> <%= form_for current_user, url: update_contact_settings_profile_index_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<div class="grid md:grid-cols-2 gap-4"> <div class="grid md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
@ -20,13 +17,7 @@
<%= f.label :email, 'Email', class: 'label' %> <%= f.label :email, 'Email', class: 'label' %>
<%= f.email_field :email, autocomplete: 'off', class: 'base-input' %> <%= f.email_field :email, autocomplete: 'off', class: 'base-input' %>
</div> </div>
<%= f.fields_for :account do |ff| %> <div class="form-control pt-2">
<div class="form-control">
<%= ff.label :name, 'Company Name', class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input' %>
</div>
<% end %>
<div class="form-control">
<%= 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 %>
@ -40,16 +31,7 @@
<%= f.label :password_confirmation, 'Confirm new password', class: 'label' %> <%= f.label :password_confirmation, 'Confirm new password', class: 'label' %>
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'base-input' %> <%= f.password_field :password_confirmation, autocomplete: 'off', class: 'base-input' %>
</div> </div>
<div class="form-control"> <div class="form-control pt-2">
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
</div>
<% end %>
<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| %>
<div class="form-control">
<%= f.text_field :value, required: true, class: 'base-input' %>
</div>
<div class="form-control">
<%= 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 %>

@ -1 +1,18 @@
Email has bee sent <div class="max-w-md mx-auto px-2 mt-12 mb-4">
<div class="space-y-6 mx-auto">
<div class="space-y-6">
<div class="flex items-center justify-center">
<a href="/" class="flex items-center">
<div class="mr-3">
<%= render 'shared/logo', width: '50px', height: '50px' %>
</div>
<h1 class="text-5xl font-bold text-center">DocuSeal</h1>
</a>
</div>
<div class="text-center text-4xl font-bold">
Email has been sent
</div>
</div>
</div>
</div>
<%= render 'shared/attribution' %>

@ -14,17 +14,21 @@
</div> </div>
</div> </div>
<% end %> <% end %>
<%= f.fields_for @user do |ff| %>
<div class="form-control">
<%= ff.label :email, class: 'label' %>
<%= ff.email_field :email, required: true, class: 'base-input' %>
</div>
<% end %>
<%= f.fields_for @account do |ff| %> <%= f.fields_for @account do |ff| %>
<set-timezone data-input-id="_account_timezone"></set-timezone>
<%= ff.hidden_field :timezone %>
<div class="form-control"> <div class="form-control">
<%= ff.label :name, 'Company name', class: 'label' %> <%= ff.label :name, 'Company name', class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input' %> <%= ff.text_field :name, required: true, class: 'base-input' %>
</div> </div>
<% end %> <% end %>
<%= f.fields_for @user do |ff| %> <%= f.fields_for @user do |ff| %>
<div class="form-control">
<%= ff.label :email, class: 'label' %>
<%= ff.email_field :email, required: true, class: 'base-input' %>
</div>
<div class="form-control"> <div class="form-control">
<%= ff.label :password, class: 'label' %> <%= ff.label :password, class: 'label' %>
<%= ff.password_field :password, required: true, placeholder: '************', class: 'base-input' %> <%= ff.password_field :password, required: true, placeholder: '************', class: 'base-input' %>

@ -1,8 +0,0 @@
<div class="card bg-base-200">
<div class="card-body text-center">
<div class="max-w-md mx-auto">
<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>
</div>
</div>
</div>

@ -2,7 +2,7 @@
<% if @pagy.pages > 1 %> <% if @pagy.pages > 1 %>
<div class="flex my-6 justify-center md:justify-between"> <div class="flex my-6 justify-center md:justify-between">
<div class="hidden md:block text-sm"> <div class="hidden md:block text-sm">
<%= @pagy.from %>-<%= @pagy.to %> of <%= @pagy.count %> of <%= local_assigns[:items_name] || 'items' %> <%= @pagy.from %>-<%= @pagy.to %> of <%= @pagy.count %> <%= local_assigns[:items_name] || 'items' %>
</div> </div>
<div class="join"> <div class="join">
<% if @pagy.prev %> <% if @pagy.prev %>

@ -6,6 +6,9 @@
<li> <li>
<%= link_to 'Profile', settings_profile_index_path, class: 'text-base hover:bg-base-300' %> <%= link_to 'Profile', settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
</li> </li>
<li>
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
</li>
<li> <li>
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %> <%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
</li> </li>
@ -13,7 +16,7 @@
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %> <%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
</li> </li>
<li> <li>
<%= link_to 'eSign', settings_esign_index_path, class: 'text-base hover:bg-base-300' %> <%= link_to 'Signature', settings_esign_index_path, class: 'text-base hover:bg-base-300' %>
</li> </li>
<li> <li>
<%= link_to 'Team', settings_users_path, class: 'text-base hover:bg-base-300' %> <%= link_to 'Team', settings_users_path, class: 'text-base hover:bg-base-300' %>

@ -1,4 +1,37 @@
<p> <div class="max-w-md mx-auto px-2 mt-12 mb-4">
Form has been submitted already - thanks! <div class="space-y-6 mx-auto">
</p> <div class="space-y-6">
<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending'), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, form: { onsubmit: 'event.submitter.disabled = true' } %> <div class="flex items-center justify-center">
<a href="/" class="flex items-center">
<div class="mr-3">
<%= render 'shared/logo', width: '50px', height: '50px' %>
</div>
<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">
<div class="mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" 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 d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
<path d="M16 7h4" />
</svg>
</div>
<div>
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-sm">Signed on <%= l(@submitter.completed_at.to_date, format: :long, locale: @submitter.submission.template.account.locale) %></p>
</div>
</div>
</div>
</div>
<div class="text-center text-2xl font-semibold">
Form has been submitter already
</div>
<div>
<%= 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' %>
</div>
</div>
</div>
<%= render 'shared/attribution' %>

@ -10,10 +10,10 @@
<h1 class="text-5xl font-bold text-center">DocuSeal</h1> <h1 class="text-5xl font-bold text-center">DocuSeal</h1>
</a> </a>
<p class="text-xl font-semibold text-center">You have been invited to submit the form</p> <p class="text-xl font-semibold text-center">You have been invited to submit the form</p>
<div class="flex items-center bg-neutral rounded-xl p-4"> <div class="flex items-center bg-base-content/10 rounded-xl p-4">
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-3"> <div class="mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-gray-100 " width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" 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 stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" /> <path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" /> <path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
@ -21,8 +21,8 @@
</svg> </svg>
</div> </div>
<div> <div>
<p class="text-gray-100 font-bold mb-1"><%= @template.name %></p> <p class="font-bold mb-1"><%= @template.name %></p>
<p class="text-sm text-gray-300">Invited by <%= @template.account.name %></p> <p class="text-sm">Invited by <%= @template.account.name %></p>
</div> </div>
</div> </div>
</div> </div>

@ -2,9 +2,6 @@
<%= render 'shared/settings_nav' %> <%= render 'shared/settings_nav' %>
<div class="flex-grow max-w-xl mx-auto"> <div class="flex-grow max-w-xl mx-auto">
<h1 class="text-4xl font-bold mb-4">Storage</h1> <h1 class="text-4xl font-bold mb-4">Storage</h1>
<p class="mb-4">
Select files storage option (TODO)
</p>
<% value = @encrypted_config.value || { 'service' => 'disk' } %> <% value = @encrypted_config.value || { 'service' => 'disk' } %>
<% configs = value['configs'] || {} %> <% configs = value['configs'] || {} %>
<%= form_for @encrypted_config, url: settings_storage_index_path, method: :post, html: { autocomplete: 'off', class: 'w-full' } do |f| %> <%= form_for @encrypted_config, url: settings_storage_index_path, method: :post, html: { autocomplete: 'off', class: 'w-full' } do |f| %>
@ -29,10 +26,10 @@
<disable-hidden id="aws_s3" class="block my-4 space-y-4 <%= 'hidden' if value['service'] != 'aws_s3' %>"> <disable-hidden id="aws_s3" class="block my-4 space-y-4 <%= 'hidden' if value['service'] != 'aws_s3' %>">
<%= render 'aws_form', f:, configs:, value: %> <%= render 'aws_form', f:, configs:, value: %>
</disable-hidden> </disable-hidden>
<disable-hidden id="google" class="block mt-4 space-y-4 mb-8 <%= 'hidden' if value['service'] != 'google' %>"> <disable-hidden id="google" class="block mt-4 space-y-4 mb-6 <%= 'hidden' if value['service'] != 'google' %>">
<%= render 'google_cloud_form', f:, configs:, value: %> <%= render 'google_cloud_form', f:, configs:, value: %>
</disable-hidden> </disable-hidden>
<disable-hidden id="azure" class="block mt-4 space-y-4 mb-8 <%= 'hidden' if value['service'] != 'azure' %>"> <disable-hidden id="azure" class="block mt-4 space-y-4 mb-6 <%= 'hidden' if value['service'] != 'azure' %>">
<%= render 'azure_form', f:, configs:, value: %> <%= render 'azure_form', f:, configs:, value: %>
</disable-hidden> </disable-hidden>
<div class="form-control"> <div class="form-control">

@ -25,7 +25,7 @@
</div> </div>
<% elsif field['type'] == 'date' %> <% elsif field['type'] == 'date' %>
<div class="flex items-center px-0.5"> <div class="flex items-center px-0.5">
<%= l(Date.parse(value)) %> <%= l(Date.parse(value), format: :long, locale: local_assigns[:locale]) %>
</div> </div>
<% else %> <% else %>
<div class="flex items-center px-0.5"> <div class="flex items-center px-0.5">

@ -1,9 +1,9 @@
<div style="max-width: 1600px" class="mx-auto pl-4"> <div style="max-width: 1600px" class="mx-auto pl-4">
<div class="flex justify-between py-1.5 items-center pr-4"> <div class="flex justify-between py-1.5 items-center pr-4">
<div class="flex space-x-3"> <a href="<%= template_path(@submission.template) %>" class="flex space-x-3">
<a href="/"><%= render 'shared/logo' %></a> <span><%= render 'shared/logo' %></span>
<span class="text-3xl font-semibold focus:text-clip"><%= @submission.template.name %></span> <span class="text-3xl font-semibold focus:text-clip"><%= @submission.template.name %></span>
</div> </a>
<div class="space-x-3 flex items-center"> <div class="space-x-3 flex items-center">
<% if last_submitter = @submission.submitters.select(&:completed_at?).max_by(&:completed_at) %> <% if last_submitter = @submission.submitters.select(&:completed_at?).max_by(&:completed_at) %>
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug) %>" class="base-button"> <download-button data-src="<%= submitter_download_index_path(last_submitter.slug) %>" class="base-button">
@ -45,7 +45,7 @@
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %> <% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
<% value = values[field['uuid']] %> <% value = values[field['uuid']] %>
<% next if value.blank? %> <% next if value.blank? %>
<%= render 'submissions/value', area:, field:, attachments_index:, value: %> <%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: current_account.locale %>
<% end %> <% end %>
</div> </div>
</div> </div>
@ -80,7 +80,7 @@
</div> </div>
<% if submitter && !submitter.completed_at? %> <% if submitter && !submitter.completed_at? %>
<div class="mt-2 mb-1"> <div class="mt-2 mb-1">
<a class="btn btn-xs btn-primary w-full" target="_blank" href="<%= submit_form_url(slug: submitter.slug) %>"> <a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_url(slug: submitter.slug) %>">
Submit Form Submit Form
</a> </a>
</div> </div>
@ -118,7 +118,7 @@
<% elsif field['type'] == 'checkbox' %> <% elsif field['type'] == 'checkbox' %>
<%= svg_icon('check', class: 'w-6 h-6') %> <%= svg_icon('check', class: 'w-6 h-6') %>
<% elsif field['type'] == 'date' %> <% elsif field['type'] == 'date' %>
<%= l(Date.parse(value)) %> <%= l(Date.parse(value), locale: current_account.locale, format: :long) %>
<% else %> <% else %>
<%= Array.wrap(value).join(', ') %> <%= Array.wrap(value).join(', ') %>
<% end %> <% end %>

@ -12,7 +12,7 @@
<div class="flex items-center bg-base-300 rounded-xl p-4 mb-4"> <div class="flex items-center bg-base-300 rounded-xl p-4 mb-4">
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-3"> <div class="mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-gray-700 " width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10" 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 stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" /> <path d="M3 19c3.333 -2 5 -4 5 -6c0 -3 -1 -3 -2 -3s-2.032 1.085 -2 3c.034 2.048 1.658 2.877 2.5 4c1.5 2 2.5 2.5 3.5 1c.667 -1 1.167 -1.833 1.5 -2.5c1 2.333 2.333 3.5 4 3.5h2.5" />
<path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" /> <path d="M20 17v-12c0 -1.121 -.879 -2 -2 -2s-2 .879 -2 2v12l2 2l2 -2z" />
@ -20,8 +20,8 @@
</svg> </svg>
</div> </div>
<div> <div>
<p class="text-lg text-gray-700 font-bold mb-1"><%= @submitter.submission.template.name %></p> <p class="text-lg 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">Signed on <%= l(@submitter.completed_at.to_date, format: :long, locale: @submitter.submission.template.account.locale) %></p>
</div> </div>
</div> </div>
</div> </div>

@ -1,5 +1,5 @@
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template) %> <% fields_index = Templates.build_field_areas_index(@submitter.submission.template) %>
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %> <% 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) %> <% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<div class="mx-auto block pb-72" style="max-width: 1000px"> <div class="mx-auto block pb-72" style="max-width: 1000px">
<div class="mt-4 flex"> <div class="mt-4 flex">
@ -17,7 +17,7 @@
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %> <% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
<% value = values[field['uuid']] %> <% value = values[field['uuid']] %>
<% next if value.blank? %> <% next if value.blank? %>
<%= render 'submissions/value', area:, field:, attachments_index:, value: %> <%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submitter.submission.template.account.locale %>
<% end %> <% end %>
</div> </div>
</div> </div>
@ -28,7 +28,7 @@
<div class="fixed bottom-0 w-full h-0 z-20"> <div class="fixed bottom-0 w-full h-0 z-20">
<div class="mx-auto" style="max-width: 1000px"> <div class="mx-auto" style="max-width: 1000px">
<div class="relative md:mx-32"> <div class="relative md:mx-32">
<div class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border border-base-200 p-4 rounded"> <div class="shadow-md bg-base-100 absolute bottom-0 md:bottom-4 w-full border border-base-200 border p-4 rounded">
<submission-form data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" 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-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" 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>
</div> </div>
</div> </div>

@ -16,18 +16,22 @@
<% end %> <% end %>
</div> </div>
</div> </div>
<% if !@pagy.count.zero? || @template.submitters.to_a.size == 1 %>
<div class="flex justify-between mb-4 items-end"> <div class="flex justify-between mb-4 items-end">
<p class="text-3xl font-bold">Submissions</p> <p class="text-3xl font-bold">Submissions</p>
<div class="flex space-x-2"> <div class="flex space-x-2">
<% if @template.submitters.to_a.size == 1 %> <% if @template.submitters.to_a.size == 1 %>
<%= 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' %> <%= 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' %>
<% end %> <% end %>
<% unless @pagy.count.zero? %>
<%= link_to new_template_submission_path(@template), class: 'btn btn-primary text-base', data: { turbo_frame: 'modal' } do %> <%= link_to new_template_submission_path(@template), class: 'btn btn-primary text-base', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %> <%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<span class="hidden md:block">Add Recipients</span> <span class="hidden md:block">Add Recipients</span>
<% end %> <% end %>
<% end %>
</div> </div>
</div> </div>
<% end %>
<% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %> <% status_badges = { 'awaiting' => 'badge-info', 'sent' => 'badge-info', 'completed' => 'badge-success', 'opened' => 'badge-warning' } %>
<% if @submissions.present? %> <% if @submissions.present? %>
<div class="space-y-4"> <div class="space-y-4">
@ -46,9 +50,11 @@
</div> </div>
</div> </div>
<div class="flex space-x-2 items-center"> <div class="flex space-x-2 items-center">
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1"> <span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1">
<%= submitter.status %> <%= submitter.status %>
</span> </span>
</div>
<% if submitter.completed_at? %> <% if submitter.completed_at? %>
<form onsubmit="event.preventDefault()"> <form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()"> <button onclick="event.stopPropagation()">
@ -65,7 +71,7 @@
</button> </button>
</form> </form>
<% else %> <% 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' %> <%= render 'shared/clipboard_copy', text: submit_form_url(slug: submission.submitters.first.slug), class: 'btn btn-sm btn-neutral text-white w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Link' %>
<% end %> <% end %>
<%= 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()' %> <%= 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()' %>
</div> </div>
@ -82,10 +88,12 @@
</span> </span>
<% unless submission.submitters.all?(&:completed_at?) %> <% unless submission.submitters.all?(&:completed_at?) %>
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge w-24 <%= status_badges[submitter.status] %> btn-xs uppercase text-xs font-medium border-1"> <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>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-xs text-xs btn-neutral text-white w-32 flex', icon_class: 'w-4 h-4 text-white', copy_title: 'Copy Link' %>
</div> </div>
<% end %> <% end %>
</div> </div>
@ -93,9 +101,11 @@
</div> </div>
<div class="flex space-x-2 items-center"> <div class="flex space-x-2 items-center">
<% if submission.submitters.all?(&:completed_at?) %> <% if submission.submitters.all?(&:completed_at?) %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1"> <span class="badge <%= status_badges[submitter.status] %> w-32 badge-lg btn-sm uppercase text-sm font-medium border-1">
<%= submitter.status %> <%= submitter.status %>
</span> </span>
</div>
<form onsubmit="event.preventDefault()"> <form onsubmit="event.preventDefault()">
<button onclick="event.stopPropagation()"> <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"> <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">
@ -117,7 +127,18 @@
</a> </a>
<% end %> <% end %>
</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' %> <div class="card bg-base-200">
<div class="card-body text-center py-16">
<div class="max-w-md mx-auto">
<p class="text-3xl font-bold text-base-content mb-4">There are no Submissions yet</p>
<p class="text-gray-600">Send an invitation to fill and submit the documents via email</p>
<%= link_to new_template_submission_path(@template), class: 'base-button mt-6', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('plus', class: 'w-6 h-6 stroke-2') %>
<span class="hidden md:block">Add Recipients</span>
<% end %>
</div>
</div>
</div>
<% end %> <% end %>

@ -17,7 +17,7 @@
<%= f.password_field :password, required: user.new_record?, class: 'base-input' %> <%= f.password_field :password, required: user.new_record?, class: 'base-input' %>
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control pt-2">
<%= f.button button_title, class: 'base-button' %> <%= f.button button_title, class: 'base-button' %>
</div> </div>
<% end %> <% end %>

@ -43,7 +43,7 @@
</span> </span>
</td> </td>
<td> <td>
<%= user.last_sign_in_at ? l(user.last_sign_in_at, format: :short) : '-' %> <%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %>
</td> </td>
<td class="flex items-center space-x-2 justify-end"> <td class="flex items-center space-x-2 justify-end">
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: 'Edit', data: { turbo_frame: 'modal' } do %> <%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: 'Edit', data: { turbo_frame: 'modal' } do %>
@ -58,6 +58,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<%= render 'shared/pagination', pagy: @pagy, items_name: 'Users' %> <%= render 'shared/pagination', pagy: @pagy, items_name: 'users' %>
</div> </div>
</div> </div>

@ -20,6 +20,9 @@ module DocuSeal
config.active_storage.routes_prefix = '' config.active_storage.routes_prefix = ''
config.i18n.available_locales = %i[en en-US en-GB es-ES pt-PT de-DE]
config.i18n.fallbacks = [:en]
config.action_view.frozen_string_literal = true config.action_view.frozen_string_literal = true
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater config.middleware.insert_before ActionDispatch::Static, Rack::Deflater

@ -6,7 +6,7 @@ if ENV['RAILS_ENV'] == 'production' && ENV['SECRET_KEY_BASE'].to_s.empty?
require 'dotenv' require 'dotenv'
require 'securerandom' require 'securerandom'
dotenv_path = './docuseal.env' dotenv_path = "#{ENV.fetch('WORKDIR', '.')}/docuseal.env"
unless File.exist?(dotenv_path) unless File.exist?(dotenv_path)
default_env = <<~TEXT default_env = <<~TEXT

@ -56,6 +56,7 @@ Rails.application.routes.draw do
resources :email, only: %i[index create], controller: 'email_settings' resources :email, only: %i[index create], controller: 'email_settings'
resources :esign, only: %i[index create], controller: 'esign_settings' resources :esign, only: %i[index create], controller: 'esign_settings'
resources :users, only: %i[index] resources :users, only: %i[index]
resource :account, only: %i[show update]
resources :profile, only: %i[index] do resources :profile, only: %i[index] do
collection do collection do
patch :update_contact patch :update_contact

@ -4,6 +4,8 @@ class CreateAccounts < ActiveRecord::Migration[7.0]
def change def change
create_table :accounts do |t| create_table :accounts do |t|
t.string :name, null: false t.string :name, null: false
t.string :timezone, null: false
t.string :locale, null: false
t.timestamps t.timestamps
end end

@ -16,6 +16,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do
create_table "accounts", force: :cascade do |t| create_table "accounts", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "timezone", null: false
t.string "locale", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Accounts
module_function
def normalize_timezone(timezone)
tzinfo = TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[timezone] || timezone)
::ActiveSupport::TimeZone.all.find { |e| e.tzinfo == tzinfo }&.name || timezone
end
end

@ -135,7 +135,7 @@ module Submissions
height - (area['y'] * height)) height - (area['y'] * height))
end end
else else
value = I18n.l(Date.parse(value)) if field['type'] == 'date' value = I18n.l(Date.parse(value), format: :long) if field['type'] == 'date'
text = HexaPDF::Layout::TextFragment.create(Array.wrap(value).join(', '), font: pdf.fonts.add(FONT_NAME), text = HexaPDF::Layout::TextFragment.create(Array.wrap(value).join(', '), font: pdf.fonts.add(FONT_NAME),
font_size: FONT_SIZE) font_size: FONT_SIZE)

@ -8,6 +8,6 @@ module.exports = {
'./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/shared/_attribution.html.erb',
'./app/views/send_submission_copy/**/*.erb' './app/views/send_submission_email/**/*.erb'
] ]
} }

Loading…
Cancel
Save