Compare commits

..

19 Commits

Author SHA1 Message Date
Oleksandr Turchyn 8b035f80b3 fix i18n typos 2024-09-23 11:45:07 +03:00
Pete Matsyburka c717293306 fix form preview 2024-09-23 11:45:07 +03:00
Pete Matsyburka b3f3cf2aa6 fix label 2024-09-23 11:45:07 +03:00
Pete Matsyburka 7406334f77 adjust i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 3e1350a221 upgrade puma 2024-09-23 11:45:07 +03:00
Pete Matsyburka 961e0a1c89 fix i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 2ce9bbecb9 fix cells field 2024-09-23 11:45:07 +03:00
Pete Matsyburka 704da3a2ec adjust email edit button 2024-09-23 11:45:07 +03:00
Pete Matsyburka 2f530ccfca i18n emails 2024-09-23 11:45:07 +03:00
Pete Matsyburka e6c20f5ff6 adjust submit button 2024-09-23 11:45:07 +03:00
Pete Matsyburka d267978437 set emails copy duration 2024-09-23 11:45:07 +03:00
Oleksandr Turchyn e9d728fa4a add i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 3c60fb4ee6 adjust show continue 2024-09-23 11:45:07 +03:00
Oleksandr Turchyn f1e494b669 adjust wording 2024-09-23 11:45:07 +03:00
Pete Matsyburka f7a8e4102b fix scroll 2024-09-23 11:45:07 +03:00
dependabot[bot] 311c2b0e34 Bump devise-two-factor in the bundler group across 1 directory
Bumps the bundler group with 1 update in the / directory: [devise-two-factor](https://github.com/tinfoil/devise-two-factor).


Updates `devise-two-factor` from 5.0.0 to 6.0.0
- [Changelog](https://github.com/devise-two-factor/devise-two-factor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tinfoil/devise-two-factor/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: devise-two-factor
  dependency-type: direct:production
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 11:45:07 +03:00
Pete Matsyburka 5dd59f4f7c optimize image 2024-09-23 11:45:07 +03:00
Pete Matsyburka 571f5d8221 set font style 2024-09-23 11:45:07 +03:00
Pete Matsyburka 136b0eb5ed fix typo 2024-09-23 11:45:07 +03:00
155 changed files with 1448 additions and 739 deletions
+11 -11
View File
@@ -165,7 +165,7 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (5.0.0)
devise-two-factor (6.0.0)
activesupport (~> 7.0)
devise (~> 4.0)
railties (~> 7.0)
@@ -269,13 +269,13 @@ GEM
openssl (>= 2.2.1)
htmlentities (4.3.4)
httpclient (2.8.3)
i18n (1.14.5)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.7.2)
irb (1.13.1)
irb (1.14.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
@@ -312,7 +312,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.7)
minitest (5.24.1)
minitest (5.25.1)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.4.0)
@@ -329,7 +329,7 @@ GEM
timeout
net-smtp (0.4.0)
net-protocol
nio4r (2.7.1)
nio4r (2.7.3)
nokogiri (1.16.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@@ -367,7 +367,7 @@ GEM
psych (5.1.2)
stringio
public_suffix (5.0.5)
puma (6.4.2)
puma (6.4.3)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.7)
@@ -418,12 +418,12 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.2.1)
rdoc (6.6.3.1)
rdoc (6.7.0)
psych (>= 4.0.0)
redis-client (0.22.2)
connection_pool
regexp_parser (2.9.2)
reline (0.5.7)
reline (0.5.10)
io-console (~> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
@@ -516,11 +516,11 @@ GEM
smart_properties (1.17.0)
sqlite3 (1.7.3)
mini_portile2 (~> 2.8.0)
stringio (3.1.0)
stringio (3.1.1)
strip_attributes (1.13.0)
activemodel (>= 3.0, < 8.0)
strscan (3.1.0)
thor (1.3.1)
thor (1.3.2)
timeout (0.4.1)
trailblazer-option (0.1.2)
turbo-rails (2.0.5)
@@ -555,7 +555,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.14)
zeitwerk (2.6.18)
PLATFORMS
aarch64-linux
+5 -4
View File
@@ -24,7 +24,7 @@ class AccountsController < ApplicationController
@encrypted_config.assign_attributes(app_url_params)
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, 'should be a valid URL')
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :show, status: :unprocessable_entity
end
@@ -33,7 +33,7 @@ class AccountsController < ApplicationController
Docuseal.refresh_default_url_options!
redirect_to settings_account_path, notice: 'Account information has been updated'
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity
end
@@ -43,11 +43,12 @@ class AccountsController < ApplicationController
true_user.update!(locked_at: Time.current)
# rubocop:disable Layout/LineLength
render turbo_stream: turbo_stream.replace(
:account_delete_button,
html: helpers.tag.p('Your account removal request will be processed within 2 weeks. ' \
'Please contact us if you want to keep your account.')
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
)
# rubocop:enable Layout/LineLength
end
private
+1 -1
View File
@@ -97,7 +97,7 @@ module Api
:name,
:external_id,
{
submitters: [%i[name uuid is_requester invite_by_uuid invite_by_uuid linked_to_uuid email]],
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
+1 -1
View File
@@ -12,6 +12,6 @@ class ApiSettingsController < ApplicationController
current_user.access_token.save!
redirect_back(fallback_location: settings_api_index_path, notice: 'API token as been updated.')
redirect_back(fallback_location: settings_api_index_path, notice: I18n.t('api_token_has_been_updated'))
end
end
+2 -1
View File
@@ -79,7 +79,8 @@ class ApplicationController < ActionController::Base
redirect_to setup_index_path unless User.exists?
end
def button_title(title: 'Submit', disabled_with: 'Submitting', title_class: '', icon: nil, icon_disabled: nil)
def button_title(title: I18n.t('submit'), disabled_with: I18n.t('submitting'), title_class: '', icon: nil,
icon_disabled: nil)
render_to_string(partial: 'shared/button_title',
locals: { title:, disabled_with:, title_class:, icon:, icon_disabled: })
end
+1 -1
View File
@@ -33,7 +33,7 @@ class DashboardController < ApplicationController
account_id: current_user.account_id,
key: AccountConfig::FORCE_MFA)
redirect_to mfa_setup_path, notice: 'Setup 2FA to continue'
redirect_to mfa_setup_path, notice: I18n.t('setup_2fa_to_continue')
end
def maybe_render_landing
@@ -11,7 +11,7 @@ class EmailSmtpSettingsController < ApplicationController
if @encrypted_config.update(email_configs)
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
redirect_to settings_email_index_path, notice: 'Changes have been saved'
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
else
render :index, status: :unprocessable_entity
end
+4 -4
View File
@@ -48,7 +48,7 @@ class EsignSettingsController < ApplicationController
if (@encrypted_config.value && @encrypted_config.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
@cert_record.name == DEFAULT_CERT_NAME
@cert_record.errors.add(:name, 'already exists')
@cert_record.errors.add(:name, I18n.t('already_exists'))
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
status: :unprocessable_entity
@@ -56,7 +56,7 @@ class EsignSettingsController < ApplicationController
save_new_cert!(@encrypted_config, @cert_record)
redirect_to settings_esign_path, notice: 'Certificate has been successfully added!'
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added_')
rescue OpenSSL::PKCS12::PKCS12Error => e
Rollbar.error(e) if defined?(Rollbar)
@@ -73,7 +73,7 @@ class EsignSettingsController < ApplicationController
@encrypted_config.save!
redirect_to settings_esign_path, notice: 'Default certificate has been selected'
redirect_to settings_esign_path, notice: I18n.t('default_certificate_has_been_selected')
end
def destroy
@@ -81,7 +81,7 @@ class EsignSettingsController < ApplicationController
@encrypted_config.save!
redirect_to settings_esign_path, notice: 'Certificate has been removed'
redirect_to settings_esign_path, notice: I18m.t('certificate_has_been_removed')
end
private
+5 -5
View File
@@ -18,11 +18,11 @@ class MfaSetupController < ApplicationController
current_user.otp_required_for_login = true
current_user.save!
redirect_to settings_profile_index_path, notice: '2FA has been configured'
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
else
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
@error_message = 'Code is invalid'
@error_message = I18n.t('code_is_invalid')
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity
end
@@ -32,9 +32,9 @@ class MfaSetupController < ApplicationController
if current_user.validate_and_consume_otp!(params[:otp_attempt])
current_user.update!(otp_required_for_login: false, otp_secret: nil)
redirect_to settings_profile_index_path, notice: '2FA has been removed'
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
else
@error_message = 'Code is invalid'
@error_message = I18n.t('code_is_invalid')
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity
end
@@ -43,7 +43,7 @@ class MfaSetupController < ApplicationController
private
def set_provision_url
return redirect_to root_path, alert: '2FA has been set up already' if current_user.otp_required_for_login
return redirect_to root_path, alert: I18n.t('2fa_has_been_set_up_already') if current_user.otp_required_for_login
current_user.otp_secret ||= User.generate_otp_secret
@@ -13,9 +13,9 @@ class NotificationsSettingsController < ApplicationController
def create
if @account_config.value.present? ? @account_config.save : @account_config.delete
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
end
end
@@ -30,7 +30,7 @@ class PersonalizationSettingsController < ApplicationController
@account_config.save!
end
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
redirect_back(fallback_location: settings_personalization_path, notice: I18n.t('settings_have_been_saved'))
end
private
+2 -2
View File
@@ -9,7 +9,7 @@ class ProfileController < ApplicationController
def update_contact
if current_user.update(contact_params)
redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
else
render :index, status: :unprocessable_entity
end
@@ -18,7 +18,7 @@ class ProfileController < ApplicationController
def update_password
if current_user.update(password_params)
bypass_sign_in(current_user)
redirect_to settings_profile_index_path, notice: 'Password has been changed'
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
else
render :index, status: :unprocessable_entity
end
@@ -7,6 +7,8 @@ class SendSubmissionEmailController < ApplicationController
skip_before_action :verify_authenticity_token
skip_authorization_check
SEND_DURATION = 30.minutes
def success; end
def create
@@ -23,7 +25,10 @@ class SendSubmissionEmailController < ApplicationController
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter,
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
end
respond_to do |f|
f.html { redirect_to success_send_submission_email_index_path }
+1 -1
View File
@@ -12,7 +12,7 @@ class SessionsController < Devise::SessionsController
Rollbar.warning('Sign in new user') if defined?(Rollbar)
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
notice: 'Create a new account'
notice: I18n.t('create_a_new_account')
end
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
+3 -3
View File
@@ -21,7 +21,7 @@ class SetupController < ApplicationController
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, 'should be a valid URL')
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :index, status: :unprocessable_entity
end
@@ -66,10 +66,10 @@ class SetupController < ApplicationController
end
def redirect_to_root_if_signed
redirect_to root_path, notice: 'You are already signed in'
redirect_to root_path, notice: I18n.t('you_are_already_signed_in')
end
def ensure_first_user_not_created!
redirect_to new_user_session_path, notice: 'Please sign in.' if User.exists?
redirect_to new_user_session_path, notice: I18n.t('please_sign_in') if User.exists?
end
end
+1 -1
View File
@@ -24,7 +24,7 @@ class StartFormController < ApplicationController
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
else
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
@error_message = 'Not found'
@error_message = I18n.t('not_found')
return render :show
end
@@ -11,7 +11,7 @@ class StorageSettingsController < ApplicationController
if @encrypted_config.update(storage_configs)
LoadActiveStorageConfigs.reload
redirect_to settings_storage_index_path, notice: 'Changes have been saved'
redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved')
else
render :index, status: :unprocessable_entity
end
+3 -3
View File
@@ -49,7 +49,7 @@ class SubmissionsController < ApplicationController
Submissions.send_signature_requests(submissions)
redirect_to template_path(@template), notice: 'New recipients have been added'
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
end
def destroy
@@ -57,13 +57,13 @@ class SubmissionsController < ApplicationController
if params[:permanently].present?
@submission.destroy!
'Submission has been removed'
I18n.t('submission_has_been_removed')
else
@submission.update!(archived_at: Time.current)
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
'Submission has been archived'
I18n.t('submission_has_been_archived')
end
redirect_back(fallback_location: template_path(@submission.template), notice:)
@@ -19,7 +19,7 @@ class SubmissionsPreviewController < ApplicationController
@submission ||= Submission.find_by!(slug: params[:slug])
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
raise ActionController::RoutingError, 'Not Found'
raise ActionController::RoutingError, I18n.t('not_found')
end
if !submission_valid_ttl?(@submission) && !signature_valid
+7 -4
View File
@@ -46,18 +46,21 @@ class SubmitFormController < ApplicationController
submitter = Submitter.find_by!(slug: params[:slug])
if submitter.completed_at?
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
end
if submitter.template.archived_at? || submitter.submission.archived_at?
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
end
if submitter.submission.expired?
return render json: { error: 'Form has been expired.' }, status: :unprocessable_entity
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
end
return render json: { error: 'Form has been declined.' }, status: :unprocessable_entity if submitter.declined_at?
if submitter.declined_at?
return render json: { error: I18n.t('form_has_been_declined') },
status: :unprocessable_entity
end
Submitters::SubmitValues.call(submitter, params, request)
@@ -10,7 +10,7 @@ class SubmittersSendEmailController < ApplicationController
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
return redirect_back(fallback_location: submission_path(@submitter.submission),
alert: 'Email has been sent already.')
alert: I18n.t('email_has_been_sent_already'))
end
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
@@ -18,6 +18,6 @@ class SubmittersSendEmailController < ApplicationController
@submitter.sent_at ||= Time.current
@submitter.save!
redirect_back(fallback_location: submission_path(@submitter.submission), notice: 'Email has been sent')
redirect_back(fallback_location: submission_path(@submitter.submission), notice: I18n.t('email_has_been_sent'))
end
end
@@ -5,7 +5,7 @@ class TemplateDocumentsController < ApplicationController
def create
if params[:blobs].blank? && params[:files].blank?
return render json: { error: 'File is missing' }, status: :unprocessable_entity
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity
end
old_fields_hash = @template.fields.hash
@@ -28,6 +28,6 @@ class TemplateDocumentsController < ApplicationController
)
}
rescue Templates::CreateAttachments::PdfEncrypted
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity
end
end
@@ -15,9 +15,9 @@ class TemplateFoldersController < ApplicationController
def update
if @template_folder != current_account.default_template_folder &&
@template_folder.update(template_folder_params)
redirect_to folder_path(@template_folder), notice: 'Folder name has been updated'
redirect_to folder_path(@template_folder), notice: I18n.t('folder_name_has_been_updated')
else
redirect_to folder_path(@template_folder), alert: 'Unable to rename folder'
redirect_to folder_path(@template_folder), alert: I18n.t('unable_to_rename_folder')
end
end
+4 -4
View File
@@ -21,7 +21,7 @@ class TemplatesController < ApplicationController
end
def new
@template.name = "#{@base_template.name} (Clone)" if @base_template
@template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template
end
def edit
@@ -87,11 +87,11 @@ class TemplatesController < ApplicationController
if params[:permanently].present?
@template.destroy!
'Template has been removed.'
I18n.t('template_has_been_removed')
else
@template.update!(archived_at: Time.current)
'Template has been archived.'
I18n.t('template_has_been_archived')
end
redirect_back(fallback_location: root_path, notice:)
@@ -123,7 +123,7 @@ class TemplatesController < ApplicationController
if template.account == current_account
redirect_to(edit_template_path(@template))
else
redirect_back(fallback_location: root_path, notice: 'Template has been cloned')
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
end
end
@@ -9,9 +9,9 @@ class TemplatesFoldersController < ApplicationController
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
if @template.save
redirect_back(fallback_location: template_path(@template), notice: 'Document template has been moved')
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
else
redirect_back(fallback_location: template_path(@template), notice: 'Unable to move template into folder')
redirect_back(fallback_location: template_path(@template), notice: I18n.t('unable_to_move_template_into_folder'))
end
end
@@ -6,6 +6,6 @@ class TemplatesRestoreController < ApplicationController
def create
@template.update!(archived_at: nil)
redirect_to template_path(@template), notice: 'Template has been unarchived'
redirect_to template_path(@template), notice: I18n.t('template_has_been_unarchived')
end
end
@@ -33,7 +33,7 @@ class TemplatesUploadsController < ApplicationController
raise if Rails.env.local?
redirect_to root_path, alert: 'Unable to upload file'
redirect_to root_path, alert: I18n.t('unable_to_update_file')
end
private
@@ -14,12 +14,12 @@ class TimestampServerController < ApplicationController
test_timeserver_url(@encrypted_config.value) if @encrypted_config.value.present?
if @encrypted_config.value.present? ? @encrypted_config.save : @encrypted_config.delete
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
end
rescue SocketError, TimestampError, OpenSSL::Timestamp::TimestampError
redirect_back fallback_location: settings_notifications_path, alert: 'Invalid Timeserver'
redirect_back fallback_location: settings_notifications_path, alert: t('invalid_timeserver')
end
private
+4 -4
View File
@@ -9,7 +9,7 @@ class UserInitialsController < ApplicationController
def update
file = params[:file]
return redirect_to settings_profile_index_path, notice: 'Unable to save initials' if file.blank?
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
@@ -22,16 +22,16 @@ class UserInitialsController < ApplicationController
)
if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: 'Initials has been saved'
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_saved')
else
redirect_to settings_profile_index_path, notice: 'Unable to save initials'
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials')
end
end
def destroy
@user_config.destroy
redirect_to settings_profile_index_path, notice: 'Initials has been removed'
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_removed')
end
private
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
def update
file = params[:file]
return redirect_to settings_profile_index_path, notice: 'Unable to save signature' if file.blank?
return redirect_to settings_profile_index_path, notice: I18n.t('Unable to save signature') if file.blank?
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
@@ -22,16 +22,16 @@ class UserSignaturesController < ApplicationController
)
if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: 'Signature has been saved'
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
else
redirect_to settings_profile_index_path, notice: 'Unable to save signature'
redirect_to settings_profile_index_path, notice: I18n.t('Unable to save signature')
end
end
def destroy
@user_config.destroy
redirect_to settings_profile_index_path, notice: 'Signature has been removed'
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_removed')
end
private
+5 -5
View File
@@ -35,14 +35,14 @@ class UsersController < ApplicationController
if @user.save
UserMailer.invitation_email(@user).deliver_later!
redirect_back fallback_location: settings_users_path, notice: 'User has been invited'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited')
else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
end
end
def update
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
@@ -56,7 +56,7 @@ class UsersController < ApplicationController
end
if @user.update(attrs)
redirect_back fallback_location: settings_users_path, notice: 'User has been updated'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
end
@@ -64,12 +64,12 @@ class UsersController < ApplicationController
def destroy
if Docuseal.demo? || @user.id == current_user.id
return redirect_to settings_users_path, notice: 'Unable to remove user'
return redirect_to settings_users_path, notice: I18n.t('unable_to_remove_user')
end
@user.update!(archived_at: Time.current)
redirect_back fallback_location: settings_users_path, notice: 'User has been removed'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
end
private
@@ -14,6 +14,6 @@ class VerifyPdfSignatureController < ApplicationController
render turbo_stream: turbo_stream.replace('result', partial: 'result',
locals: { pdfs:, files: params[:files], trusted_certs: })
rescue HexaPDF::MalformedPDFError
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
end
end
+1 -1
View File
@@ -13,7 +13,7 @@ class WebhookSecretController < ApplicationController
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook Secret has been saved.')
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
end
private
@@ -11,7 +11,7 @@ class WebhookSettingsController < ApplicationController
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook URL has been saved.')
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
end
def update
@@ -20,7 +20,7 @@ class WebhookSettingsController < ApplicationController
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
'encrypted_config_id' => @encrypted_config.id })
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook request has been sent.')
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
end
private
+3 -3
View File
@@ -3,7 +3,7 @@
class="field-area flex absolute lg:text-base -outline-offset-1"
dir="auto"
:style="computedStyle"
:class="{ 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
:class="{ 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
>
<div
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
@@ -29,12 +29,12 @@
{{ optionValue(option) }}
</template>
<template v-else>
{{ field.name || fieldNames[field.type] }}
{{ field.title || field.name || fieldNames[field.type] }}
<template v-if="field.type === 'checkbox' && !field.name">
{{ fieldIndex + 1 }}
</template>
<template v-else-if="!field.required && field.type !== 'checkbox'">
(optional)
({{ t('optional') }})
</template>
</template>
</div>
+10 -2
View File
@@ -132,10 +132,18 @@ export default {
this.scrollInContainer(areaRef.$el)
} else {
const targetRect = areaRef.$refs.scrollToElem.getBoundingClientRect()
const root = this.$root.$el?.parentNode?.classList?.contains('ds') ? this.$root.$el : document.body
const rootRect = root.getBoundingClientRect()
const scrollEl = this.scrollEl || window
let rootRect = {}
if (this.scrollEl === document.documentElement) {
rootRect = this.scrollEl.getBoundingClientRect()
} else {
const root = this.$root.$el?.parentNode?.classList?.contains('ds') ? this.$root.$el : document.body
rootRect = root.getBoundingClientRect()
}
scrollEl.scrollTo({ top: targetRect.top - rootRect.top, behavior: 'smooth' })
}
+12 -2
View File
@@ -10,7 +10,7 @@
:height="30"
/>
<span>
{{ completedMessage.title || t('form_has_been_completed') }}
{{ completedMessage.title || (hasSignatureFields ? (hasMultipleDocuments ? t('documents_have_been_signed') : t('document_has_been_signed')) : t('form_has_been_completed')) }}
</span>
</div>
<div
@@ -88,7 +88,7 @@
v-if="attribution"
class="text-center mt-4"
>
{{ t('signed_with') }}
{{ t('powered_by') }}
<a
href="https://www.docuseal.co/start"
target="_blank"
@@ -129,6 +129,16 @@ export default {
required: false,
default: true
},
hasSignatureFields: {
type: Boolean,
required: false,
default: false
},
hasMultipleDocuments: {
type: Boolean,
required: false,
default: false
},
withDownloadButton: {
type: Boolean,
required: false,
+42 -14
View File
@@ -38,8 +38,11 @@
<template v-else-if="alwaysMinimize">
{{ t('next') }}
</template>
<template v-else-if="isShowContinue">
{{ t('continue') }}
</template>
<template v-else>
{{ t('submit_form') }}
{{ t('start_now') }}
</template>
<IconArrowsDiagonal
class="absolute right-0 mr-4"
@@ -59,7 +62,7 @@
id="minimize_form_button"
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
:title="t('minimize')"
@click.prevent="isFormVisible = false"
@click.prevent="minimizeForm"
>
<IconArrowsDiagonalMinimize2
:width="20"
@@ -201,7 +204,7 @@
<span
@click="scrollIntoField(currentField)"
>
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ submitButtonText }}</span>.
</span>
</div>
<div
@@ -265,7 +268,7 @@
>
<template v-if="isAnonymousChecboxes || !showFieldNames">
<span class="text-xl">
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? t('submit') : t('next') }}</span>.
{{ t('complete_hightlighted_checkboxes_and_click') }} <span class="font-semibold">{{ submitButtonText }}</span>.
</span>
<input
v-for="field in currentStepFields"
@@ -340,7 +343,7 @@
:remember-signature="rememberSignature"
:attachments-index="attachmentsIndex"
:require-signing-reason="requireSigningReason"
:button-text="buttonText"
:button-text="submitButtonText"
:dry-run="dryRun"
:with-disclosure="withDisclosure"
:with-qr-button="withQrButton"
@@ -349,7 +352,7 @@
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
@attached="attachments.push($event)"
@start="scrollIntoField(currentField)"
@minimize="isFormVisible = false"
@minimize="minimizeForm"
/>
<InitialsStep
v-else-if="currentField.type === 'initials'"
@@ -365,7 +368,7 @@
@attached="attachments.push($event)"
@start="scrollIntoField(currentField)"
@focus="scrollIntoField(currentField)"
@minimize="isFormVisible = false"
@minimize="minimizeForm"
/>
<AttachmentStep
v-else-if="currentField.type === 'file'"
@@ -421,7 +424,7 @@
class="mr-1 animate-spin"
/>
<span>
{{ buttonText }}
{{ submitButtonText }}
</span><span
v-if="isSubmitting"
class="w-6 flex justify-start mr-1"
@@ -449,6 +452,8 @@
v-else
:is-demo="isDemo"
:attribution="attribution"
:has-signature-fields="stepFields.some((fields) => fields.some((f) => ['signature', 'initials'].includes(f.type)))"
:has-multiple-documents="hasMultipleDocuments"
:completed-button="completedRedirectUrl ? {} : completedButton"
:completed-message="completedRedirectUrl ? {} : completedMessage"
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
@@ -759,6 +764,7 @@ export default {
isFormVisible: this.expand !== false,
showFillAllRequiredFields: false,
currentStep: 0,
isShowContinue: false,
enableScrollIntoField: true,
phoneVerifiedValues: {},
orientation: screen?.orientation?.type,
@@ -771,9 +777,15 @@ export default {
isMobile () {
return /android|iphone|ipad/i.test(navigator.userAgent)
},
buttonText () {
if (this.alwaysMinimize || this.stepFields.length === this.currentStep + 1) {
submitButtonText () {
if (this.alwaysMinimize) {
return this.t('submit')
} else if (this.stepFields.length === this.currentStep + 1) {
if (this.currentField.type === 'signature') {
return this.t('sign_and_complete')
} else {
return this.t('complete')
}
} else {
return this.t('next')
}
@@ -781,6 +793,18 @@ export default {
alwaysMinimize () {
return this.minimize || (this.orientation?.includes('landscape') && this.isMobile && parseInt(window.innerHeight) < 550)
},
hasMultipleDocuments () {
return Object.keys(
this.stepFields.reduce((acc, fields) => {
fields.forEach((f) => {
f.areas?.forEach((a) => {
acc[a.attachment_uuid] = 1
})
})
return acc
}, {})
).filter(Boolean).length > 1
},
currentStepFields () {
return this.stepFields[this.currentStep] || []
},
@@ -905,11 +929,11 @@ export default {
}
if (document.body?.clientWidth >= 768 && this.expand !== true && ['signature', 'initials', 'file', 'image'].includes(this.currentField?.type)) {
this.isFormVisible = false
this.minimizeForm()
}
if (this.alwaysMinimize) {
this.isFormVisible = false
this.minimizeForm()
}
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
@@ -1167,7 +1191,7 @@ export default {
if (nextStep) {
if (this.alwaysMinimize) {
this.isFormVisible = false
this.minimizeForm()
}
this.goToStep(this.stepFields.indexOf(nextStep), this.autoscrollFields)
@@ -1195,10 +1219,14 @@ export default {
this.isSubmitting = false
})
},
minimizeForm () {
this.isFormVisible = false
this.isShowContinue = true
},
async performComplete (resp) {
this.isCompleted = true
if (resp) {
if (resp?.text) {
const respData = await resp.text()
if (respData) {
+91 -26
View File
@@ -1,4 +1,6 @@
const en = {
complete: 'Complete',
sign_and_complete: 'Sign and Complete',
text: 'Text',
by_clicking_you_agree_to_the: 'By clicking "{button}", you agree to the',
electronic_signature_disclosure: 'Electronic Signature Disclosure',
@@ -38,7 +40,8 @@ const en = {
minimize: 'Minimize',
payment: 'Payment',
phone: 'Phone',
submit_form: 'Submit Form',
start_now: 'Start Now',
continue: 'Continue',
sign_now: 'Sign Now',
type_here_: 'Type here...',
optional: 'optional',
@@ -59,8 +62,10 @@ const en = {
type_signature_here: 'Type signature here',
type_initial_here: 'Type initials here',
form_has_been_completed: 'Form has been completed!',
document_has_been_signed: 'Document has been signed!',
documents_have_been_signed: 'Documents have been signed!',
create_a_free_account: 'Create a Free Account',
signed_with: 'Signed with',
powered_by: 'Powered by',
please_check_the_box_to_continue: 'Please check the box to continue',
open_source_documents_software: 'open source documents software',
verified_phone_number: 'Verify Phone Number',
@@ -87,6 +92,8 @@ const en = {
}
const es = {
complete: 'Completar',
sign_and_complete: 'Firmar y Completar',
invite: 'Invitar',
email: 'Correo electrónico',
approved: 'Aprobado',
@@ -126,7 +133,8 @@ const es = {
payment: 'Pago',
phone: 'Teléfono',
take_photo: 'Tomar foto',
submit_form: 'Enviar Formulario',
start_now: 'Comenzar ahora',
continue: 'Continuar',
sign_now: 'Firmar ahora',
type_here_: 'Escribe aquí...',
optional: 'opcional',
@@ -146,8 +154,10 @@ const es = {
type_signature_here: 'Escribe la firma aquí',
type_initial_here: 'Escribe las iniciales aquí',
form_has_been_completed: '¡El formulario ha sido completado!',
document_has_been_signed: '¡El documento ha sido firmado!',
documents_have_been_signed: '¡Los documentos han sido firmados!',
create_a_free_account: 'Crear una Cuenta Gratuita',
signed_with: 'Firmado con',
powered_by: 'Desarrollado por',
please_check_the_box_to_continue: 'Por favor marque la casilla para continuar',
open_source_documents_software: 'software de documentos de código abierto',
verified_phone_number: 'Verificar número de teléfono',
@@ -174,6 +184,8 @@ const es = {
}
const it = {
complete: 'Completa',
sign_and_complete: 'Firma e Completa',
invite: 'Invita',
email: 'Email',
approved: 'Approvato',
@@ -212,7 +224,8 @@ const it = {
payment: 'Pagamento',
number_phone_is_invalid: '{number} telefono non è valido',
phone: 'Telefono',
submit_form: 'Invia Modulo',
start_now: 'Inizia ora',
continue: 'Continua',
sign_now: 'Firma ora',
type_here_: 'Digita qui...',
optional: 'opzionale',
@@ -233,8 +246,10 @@ const it = {
type_signature_here: 'Scrivi la firma qui',
type_initial_here: 'Scrivi le iniziali qui',
form_has_been_completed: 'Il modulo è stato completato!',
document_has_been_signed: 'Il documento è stato firmato!',
documents_have_been_signed: 'I documenti sono stati firmati!',
create_a_free_account: 'Crea un Account Gratuito',
signed_with: 'Firmato con',
powered_by: 'Desarrollado por',
please_check_the_box_to_continue: 'Si prega di spuntare la casella per continuare',
open_source_documents_software: 'software di documenti open source',
verified_phone_number: 'Verifica numero di telefono',
@@ -261,6 +276,8 @@ const it = {
}
const de = {
complete: 'Abschließen',
sign_and_complete: 'Signieren und Abschließen',
invite: 'Einladen',
email: 'E-Mail',
approved: 'Genehmigt',
@@ -299,7 +316,8 @@ const de = {
payment: 'Zahlung',
number_phone_is_invalid: '{number} Telefonnummer ist ungültig',
phone: 'Telefon',
submit_form: 'Formular absenden',
start_now: 'Jetzt starten',
continue: 'Fortsetzen',
sign_now: 'Jetzt unterschreiben',
type_here_: 'Hier eingeben...',
optional: 'optional',
@@ -320,8 +338,10 @@ const de = {
type_signature_here: 'Unterschrift hier eingeben',
type_initial_here: 'Initialen hier eingeben',
form_has_been_completed: 'Formular wurde ausgefüllt!',
document_has_been_signed: 'Dokument wurde unterschrieben!',
documents_have_been_signed: 'Dokumente wurden unterschrieben!',
create_a_free_account: 'Kostenloses Konto erstellen',
signed_with: 'Unterschrieben mit',
powered_by: 'Bereitgestellt von',
please_check_the_box_to_continue: 'Bitte setzen Sie das Häkchen, um fortzufahren',
open_source_documents_software: 'Open-Source-Dokumentensoftware',
verified_phone_number: 'Telefonnummer überprüfen',
@@ -348,6 +368,8 @@ const de = {
}
const fr = {
complete: 'Terminer',
sign_and_complete: 'Signer et Terminer',
invite: 'Inviter',
email: 'Courriel',
approved: 'Approuvé',
@@ -386,7 +408,8 @@ const fr = {
payment: 'Paiement',
number_phone_is_invalid: '{number} téléphone est invalide',
phone: 'Téléphone',
submit_form: 'Envoyer le Formulaire',
start_now: 'Commencer maintenant',
continue: 'Continuer',
sign_now: 'Signer maintenant',
type_here_: 'Tapez ici...',
optional: 'facultatif',
@@ -407,8 +430,10 @@ const fr = {
type_signature_here: 'Tapez la signature ici',
type_initial_here: 'Tapez les initiales ici',
form_has_been_completed: 'Le formulaire a été complété !',
document_has_been_signed: 'Le document a été signé!',
documents_have_been_signed: 'Les documents ont été signés!',
create_a_free_account: 'Créer un Compte Gratuit',
signed_with: 'Signé avec',
powered_by: 'Propulsé par',
please_check_the_box_to_continue: 'Veuillez cocher la case pour continuer',
open_source_documents_software: 'logiciel de documents open source',
verified_phone_number: 'Vérifier le numéro de téléphone',
@@ -435,6 +460,8 @@ const fr = {
}
const pl = {
complete: 'Zakończ',
sign_and_complete: 'Podpisz i zakończ',
invite: 'Zaproś',
email: 'E-mail',
approved: 'Zaakceptowany',
@@ -473,7 +500,8 @@ const pl = {
payment: 'Płatność',
number_phone_is_invalid: '{number} telefon jest nieprawidłowy',
phone: 'Telefon',
submit_form: 'Wyślij Formularz',
start_now: 'Zacznij teraz',
continue: 'Kontynuuj',
sign_now: 'Podpisz teraz',
type_here_: 'Wpisz tutaj...',
optional: 'opcjonalny',
@@ -494,8 +522,10 @@ const pl = {
type_signature_here: 'Wpisz podpis tutaj',
type_initial_here: 'Wpisz inicjały tutaj',
form_has_been_completed: 'Formularz został wypełniony!',
document_has_been_signed: 'Dokument został podpisany!',
documents_have_been_signed: 'Dokumenty zostały podpisane!',
create_a_free_account: 'Utwórz darmowe konto',
signed_with: 'Podpisane za pomocą',
powered_by: 'Napędzany przez',
please_check_the_box_to_continue: 'Proszę zaznaczyć pole, aby kontynuować',
open_source_documents_software: 'oprogramowanie do dokumentów open source',
verified_phone_number: 'Zweryfikuj numer telefonu',
@@ -522,6 +552,8 @@ const pl = {
}
const uk = {
complete: 'Завершити',
sign_and_complete: 'Підписати і завершити',
invite: 'Запросити',
email: 'Електронна пошта',
approved: 'Затверджено',
@@ -560,7 +592,8 @@ const uk = {
payment: 'Платіж',
number_phone_is_invalid: '{number} телефон недійсний',
phone: 'Телефон',
submit_form: 'Надіслати Форму',
start_now: 'Почати зараз',
continue: 'Продовжити',
sign_now: 'Підписати зараз',
type_here_: 'Введіть тут',
optional: 'необов’язково',
@@ -581,8 +614,10 @@ const uk = {
type_signature_here: 'Введіть підпис тут',
type_initial_here: 'Введіть ініціали тут',
form_has_been_completed: 'Форму заповнено!',
document_has_been_signed: 'Документ був підписаний!',
documents_have_been_signed: 'Документи були підписані!',
create_a_free_account: 'Створити безкоштовний обліковий запис',
signed_with: ідписано за допомогою',
powered_by: рацює на базі',
please_check_the_box_to_continue: 'Будь ласка, позначте прапорець, щоб продовжити',
open_source_documents_software: 'відкритий програмний засіб для документів',
verified_phone_number: 'Підтвердіть номер телефону',
@@ -609,6 +644,8 @@ const uk = {
}
const cs = {
complete: 'Dokončit',
sign_and_complete: 'Podepsat a dokončit',
invite: 'Pozvat',
email: 'E-mail',
approved: 'Schváleno',
@@ -647,7 +684,8 @@ const cs = {
payment: 'Platba',
phone: 'Telefon',
number_phone_is_invalid: '{number} telefon je neplatný',
submit_form: 'Odeslat formulář',
start_now: 'Začít nyní',
continue: 'Pokračovat',
sign_now: 'Podepsat nyní',
type_here_: 'Zadejte zde',
optional: 'volitelné',
@@ -668,8 +706,10 @@ const cs = {
type_signature_here: 'Sem zadejte podpis',
type_initial_here: 'Sem zadejte iniciály',
form_has_been_completed: 'Formulář byl dokončen!',
document_has_been_signed: 'Dokument byl podepsán!',
documents_have_been_signed: 'Dokumenty byly podepsány!',
create_a_free_account: 'Vytvořit bezplatný účet',
signed_with: 'Podepsáno pomocí',
powered_by: 'Poháněno',
please_check_the_box_to_continue: 'Prosím, zaškrtněte políčko pro pokračování',
open_source_documents_software: 'open source software pro dokumenty',
verified_phone_number: 'Ověřte telefonní číslo',
@@ -696,6 +736,8 @@ const cs = {
}
const pt = {
complete: 'Completar',
sign_and_complete: 'Assinar e Completar',
invite: 'Convidar',
email: 'E-mail',
approved: 'Aprovado',
@@ -734,7 +776,8 @@ const pt = {
payment: 'Pagamento',
number_phone_is_invalid: '{number} telefone é inválido',
phone: 'Telefone',
submit_form: 'Enviar Formulário',
start_now: 'Começar agora',
continue: 'Continuar',
sign_now: 'Assinar agora',
type_here_: 'Digite aqui',
optional: 'opcional',
@@ -755,8 +798,10 @@ const pt = {
type_signature_here: 'Digite a assinatura aqui',
type_initial_here: 'Digite as iniciais aqui',
form_has_been_completed: 'O formulário foi concluído!',
document_has_been_signed: 'O documento foi assinado!',
documents_have_been_signed: 'Os documentos foram assinados!',
create_a_free_account: 'Criar uma Conta Gratuita',
signed_with: 'Assinado com',
powered_by: 'Desenvolvido por',
please_check_the_box_to_continue: 'Por favor, marque a caixa para continuar',
open_source_documents_software: 'software de documentos de código aberto',
verified_phone_number: 'Verificar Número de Telefone',
@@ -783,6 +828,8 @@ const pt = {
}
const he = {
complete: 'השלם',
sign_and_complete: 'חתום והשלם',
invite: 'הזמן',
email: 'דוא"ל',
approved: 'מאושר',
@@ -821,7 +868,8 @@ const he = {
payment: 'תשלום',
phone: 'טלפון',
number_phone_is_invalid: '{number} טלפון אינו חוקי',
submit_form: 'שלח טופס',
start_now: 'התחל עכשיו',
continue: 'המשך',
sign_now: 'חתום כעת',
type_here_: 'הקלד כאן',
optional: 'אופציונלי',
@@ -843,8 +891,10 @@ const he = {
type_signature_here: 'הקלד חתימה כאן',
type_initial_here: 'הקלד ציוני ראשי תיבות כאן',
form_has_been_completed: 'הטופס הושלם',
document_has_been_signed: 'המסמך נחתם!',
documents_have_been_signed: 'המסמכים נחתמו!',
create_a_free_account: 'צור חשבון חינם',
signed_with: 'חתום באמצעות',
powered_by: 'מופעל על ידי',
please_check_the_box_to_continue: 'אנא סמן את התיבה כדי להמשיך',
open_source_documents_software: 'תוכנה פתוחה למסמכים',
verified_phone_number: 'אימות מספר טלפון',
@@ -871,6 +921,8 @@ const he = {
}
const nl = {
complete: 'Voltooien',
sign_and_complete: 'Ondertekenen en voltooien',
invite: 'Uitnodigen',
email: 'E-mail',
approved: 'Goedgekeurd',
@@ -910,7 +962,8 @@ const nl = {
payment: 'Betaling',
phone: 'Telefoon',
number_phone_is_invalid: '{number} telefoon is ongeldig',
submit_form: 'Formulier verzenden',
start_now: 'Nu beginnen',
continue: 'Doorgaan',
sign_now: 'Nu ondertekenen',
type_here_: 'Typ hier...',
optional: 'Optioneel',
@@ -931,8 +984,10 @@ const nl = {
type_signature_here: 'Typ hier uw handtekening',
type_initial_here: 'Typ hier uw initialen',
form_has_been_completed: 'Formulier is voltooid!',
document_has_been_signed: 'Document is ondertekend!',
documents_have_been_signed: 'De documenten zijn ondertekend!',
create_a_free_account: 'Maak een gratis account aan',
signed_with: 'Ondertekend met',
powered_by: 'Aangedreven door',
please_check_the_box_to_continue: 'Vink het vakje aan om door te gaan',
open_source_documents_software: 'Open source documenten software',
verified_phone_number: 'Geverifieerd telefoonnummer',
@@ -959,6 +1014,8 @@ const nl = {
}
const ar = {
complete: 'اكتمال',
sign_and_complete: 'التوقيع والاكتمال',
invite: 'دعوة',
email: 'البريد الإلكتروني',
approved: 'موافق عليه',
@@ -997,7 +1054,8 @@ const ar = {
payment: 'الدفع',
phone: 'هاتف',
number_phone_is_invalid: '{number} هاتف غير صالح',
submit_form: 'إرسال النموذج',
start_now: 'ابدأ الآن',
continue: 'متابعة',
sign_now: 'وقع الآن',
type_here_: 'اكتب هنا',
optional: 'اختياري',
@@ -1018,8 +1076,10 @@ const ar = {
type_signature_here: 'اكتب التوقيع هنا',
type_initial_here: 'اكتب الاختصارات هنا',
form_has_been_completed: 'تم إكمال النموذج!',
document_has_been_signed: 'تم توقيع الوثيقة!',
documents_have_been_signed: 'تم توقيع الوثائق!',
create_a_free_account: 'إنشاء حساب مجاني',
signed_with: 'تم التوقيع بواسطة',
powered_by: 'مدعوم من',
please_check_the_box_to_continue: 'الرجاء التحقق من الخانة للمتابعة',
open_source_documents_software: 'برنامج وثائق مفتوح المصدر',
verified_phone_number: 'تحقق من رقم الهاتف',
@@ -1046,6 +1106,8 @@ const ar = {
}
const ko = {
complete: '완료',
sign_and_complete: '서명하고 완료하기',
invite: '초대하기',
email: '이메일',
approved: '승인됨',
@@ -1083,7 +1145,8 @@ const ko = {
payment: '결제',
phone: '전화',
number_phone_is_invalid: '{number} 전화번호가 유효하지 않습니다',
submit_form: '양식 제출',
start_now: '지금 시작하기',
continue: '계속하다',
sign_now: '지금 서명',
type_here_: '여기에 입력...',
optional: '선택 사항',
@@ -1104,8 +1167,10 @@ const ko = {
type_signature_here: '여기에 서명 입력',
type_initial_here: '여기에 이니셜 입력',
form_has_been_completed: '양식이 완료되었습니다!',
document_has_been_signed: '문서가 서명되었습니다!',
documents_have_been_signed: '문서가 서명되었습니다!',
create_a_free_account: '무료 계정 생성',
signed_with: '다음으로 서명:',
powered_by: '구동',
please_check_the_box_to_continue: '계속하려면 확인란을 선택하십시오',
open_source_documents_software: '오픈 소스 문서 소프트웨어',
verified_phone_number: '전화번호 확인됨',
+2 -2
View File
@@ -131,7 +131,7 @@
class="flex items-center justify-center space-x-2"
>
<IconEye class="w-6 h-6 flex-shrink-0" />
<span class="whitespace-nowrap">Save and Preview</span>
<span class="whitespace-nowrap">{{ t('save_and_preview') }}</span>
</a>
</li>
<li>
@@ -142,7 +142,7 @@
@click="closeDropdown"
>
<IconAdjustments class="w-6 h-6 flex-shrink-0" />
<span class="whitespace-nowrap">Preferences</span>
<span class="whitespace-nowrap">{{ t('preferences') }}</span>
</a>
</li>
</ul>
@@ -26,7 +26,7 @@
href="https://www.docuseal.co/pricing"
target="_blank"
class="link"
>Available in Pro</a>
>{{ t('available_in_pro') }}</a>
</div>
<form @submit.prevent="validateSaveAndClose">
<div class="my-4 space-y-5">
@@ -26,7 +26,7 @@
href="https://www.docuseal.co/pricing"
target="_blank"
class="link"
>Available in Pro</a>
>{{ t('available_in_pro') }}</a>
</div>
<div class="flex-inline mb-2 gap-2 space-y-1">
<button
@@ -201,7 +201,7 @@ export default {
const normalizedFormula = this.normalizeFormula(this.formula)
if (normalizedFormula.includes('FIELD NOT FOUND')) {
alert('Some fields are missing in the formula.')
alert(this.t('some_fields_are_missing_in_the_formula'))
} else {
this.field.preferences.formula = normalizedFormula
+9 -1
View File
@@ -117,7 +117,15 @@ const en = {
custom: 'Custom',
numbers_only: 'Numbers only',
letters_only: 'Letters only',
regexp_validation: 'Regexp validation'
regexp_validation: 'Regexp validation',
enter_pdf_password: 'Enter PDF password',
wrong_password: 'Wrong password',
currency: 'Currency',
save_and_preview: 'Save and Preview',
preferences: 'Preferences',
available_in_pro: 'Available in Pro',
some_fields_are_missing_in_the_formula: 'Some fields are missing in the formula.',
learn_more: 'Learn more'
}
export { en }
@@ -119,7 +119,7 @@
<div class="flex items-center pl-1">
<span
class="tooltip tooltip-top"
data-tip="Remove"
data-tip="<%= t('remove') %>"
>
<button
:disabled="mappings.filter((m) => m.submitter_uuid === submitter.uuid).length < 2"
@@ -43,7 +43,7 @@
class="absolute -top-1 left-2.5 px-1 h-4"
style="font-size: 8px"
>
Currency
{{ t('currency') }}
</label>
</div>
<div
@@ -156,7 +156,7 @@
href="https://www.docuseal.co/blog/accept-payments-and-request-signatures-with-ease"
target="_blank"
data-turbo="false"
>Learn more</a>
>{{ t('learn_more') }}</a>
</div>
<li class="mb-1">
<label
+3 -3
View File
@@ -90,10 +90,10 @@ export default {
})
} else if (resp.status === 422) {
resp.json().then((data) => {
if (data.error === 'PDF encrypted') {
if (data.status === 'pdf_encrypted') {
const formData = new FormData(this.$refs.form)
formData.append('password', prompt('Enter PDF password'))
formData.append('password', prompt(this.t('enter_pdf_password')))
this.baseFetch(`/templates/${this.templateId}/documents`, {
method: 'POST',
@@ -103,7 +103,7 @@ export default {
this.$emit('success', await resp.json())
this.$refs.input.value = ''
} else {
alert('Wrong password')
alert(this.t('wrong_password'))
}
})
}
+17 -27
View File
@@ -4,8 +4,6 @@ class SubmitterMailer < ApplicationMailer
MAX_ATTACHMENTS_SIZE = 10.megabytes
SIGN_TTL = 1.hour + 20.minutes
DEFAULT_INVITATION_SUBJECT = 'You are invited to submit a form'
NO_REPLY_REGEXP = /no-?reply@/i
def invitation_email(submitter)
@@ -21,17 +19,19 @@ class SubmitterMailer < ApplicationMailer
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
subject =
if @email_config || @subject
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
else
DEFAULT_INVITATION_SUBJECT
end
assign_message_metadata('submitter_invitation', @submitter)
reply_to = build_submitter_reply_to(@submitter)
subject =
if @email_config || @subject
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
elsif @submitter.with_signature_fields?
I18n.t(:you_are_invited_to_sign_a_document)
else
I18n.t(:you_are_invited_to_submit_a_form)
end
mail(
to: @submitter.friendly_name,
from: from_address_for_submitter(submitter),
@@ -64,15 +64,12 @@ class SubmitterMailer < ApplicationMailer
@body = @submitter.template.preferences['completed_notification_email_body'].presence
@body ||= @email_config.value['body'] if @email_config
subject =
if @subject.present?
ReplaceEmailVariables.call(@subject, submitter:)
else
build_completed_subject(submitter)
end
assign_message_metadata('submitter_completed', @submitter)
subject =
ReplaceEmailVariables.call(@subject.presence || I18n.t(:template_name_has_been_completed_by_submitters),
submitter:)
mail(from: from_address_for_submitter(submitter),
to: to || (user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name),
subject:)
@@ -116,17 +113,16 @@ class SubmitterMailer < ApplicationMailer
@body = @submitter.template.preferences['documents_copy_email_body'].presence
@body ||= @email_config.value['body'] if @email_config
assign_message_metadata('submitter_documents_copy', @submitter)
reply_to = build_submitter_reply_to(submitter)
subject =
if @subject.present?
ReplaceEmailVariables.call(@subject, submitter:)
else
'Your document copy'
I18n.t(:your_document_copy)
end
assign_message_metadata('submitter_documents_copy', @submitter)
reply_to = build_submitter_reply_to(submitter)
mail(from: from_address_for_submitter(submitter),
to: to || @submitter.friendly_name,
reply_to:,
@@ -145,12 +141,6 @@ class SubmitterMailer < ApplicationMailer
reply_to
end
def build_completed_subject(submitter)
submitters = submitter.submission.submitters.order(:completed_at)
.map { |e| e.name || e.email || e.phone }.join(', ')
%(#{submitter.submission.template.name} has been completed by #{submitters})
end
def add_completed_email_attachments!(submitter, with_audit_log: true, with_documents: true)
documents = with_documents ? Submitters.select_attachments_for_download(submitter) : []
+15 -21
View File
@@ -43,29 +43,23 @@ class AccountConfig < ApplicationRecord
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
DEFAULT_VALUES = {
SUBMITTER_INVITATION_EMAIL_KEY => {
'subject' => 'You are invited to submit a form',
'body' => "Hi there,\n\n" \
"You have been invited to submit the \"{{template.name}}\" form.\n\n" \
"{{submitter.link}}\n\n" \
"Please contact us by replying to this email if you didn't request this.\n\n" \
"Thanks,\n" \
'{{account.name}}'
SUBMITTER_INVITATION_EMAIL_KEY => lambda {
{
'subject' => I18n.t(:you_are_invited_to_sign_a_document),
'body' => I18n.t(:submitter_invitation_email_sign_body)
}
},
SUBMITTER_COMPLETED_EMAIL_KEY => {
'subject' => '{{template.name}} has been completed by {{submission.submitters}}',
'body' => "Hi,\n\n" \
"\"{{template.name}}\" form has been completed by {{submission.submitters}}\n\n" \
'{{submission.link}}'
SUBMITTER_COMPLETED_EMAIL_KEY => lambda {
{
'subject' => I18n.t(:template_name_has_been_completed_by_submitters),
'body' => I18n.t(:submitter_completed_email_body)
}
},
SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY => {
'subject' => 'Your document copy',
'body' => "Hi there,\n\n" \
"Please check the copy of your \"{{template.name}}\" submission in the email attachments.\n" \
"Alternatively, you can download your copy using:\n\n" \
"{{documents.link}}\n\n" \
"Thanks,\n" \
'{{account.name}}'
SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY => lambda {
{
'subject' => I18n.t(:your_document_copy),
'body' => I18n.t(:submitter_documents_copy_email_body)
}
}
}.freeze
+8
View File
@@ -96,4 +96,12 @@ class Submitter < ApplicationRecord
def status_event_at
declined_at || completed_at || opened_at || sent_at || created_at
end
def with_signature_fields?
@with_signature_fields ||= begin
fields = submission.template_fields || template.fields
signature_field_types = %w[signature initials]
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
end
end
end
+23 -17
View File
@@ -1,22 +1,24 @@
<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>
<h1 class="text-4xl font-bold mb-4">
<%= t('account') %>
</h1>
<%= form_for '', url: settings_account_path, method: :patch, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<%= fields_for current_account do |ff| %>
<div class="form-control">
<%= ff.label :name, 'Company Name', class: 'label' %>
<%= ff.label :name, t('company_name'), class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input', dir: 'auto' %>
</div>
<div class="grid md:grid-cols-2 gap-4">
<div class="form-control">
<%= ff.label :timezone, class: 'label' %>
<%= ff.label :timezone, t('time_zone'), class: 'label' %>
<%= ff.select :timezone, nil, {}, class: 'base-select' do %>
<%= time_zone_options_for_select(current_account.timezone) %>
<% end %>
</div>
<div class="form-control">
<%= ff.label :locale, 'Time format', class: 'label' %>
<%= ff.label :locale, t('time_format'), class: 'label' %>
<%= ff.select :locale, options_for_select(controller.class::LOCALE_OPTIONS.invert, current_account.locale), {}, class: 'base-select' %>
</div>
</div>
@@ -25,27 +27,29 @@
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) && !current_account.testing? && ENV['APP_URL'].blank? %>
<%= fields_for encrypted_config do |ff| %>
<div class="form-control">
<%= ff.label :value, 'App URL', class: 'label' %>
<%= ff.label :value, t('app_url'), class: 'label' %>
<%= ff.text_field :value, autocomplete: 'off', class: 'base-input' %>
</div>
<% end %>
<% end %>
<% if can?(:update, current_account) %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('update'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
<% end %>
<% if can?(:manage, AccountConfig) %>
<div class="px-1 mt-8">
<h2 class="text-2xl font-bold mb-2">Preferences</h2>
<h2 class="text-2xl font-bold mb-2">
<%= t('preferences') %>
</h2>
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORCE_MFA) %>
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Force 2FA with Authenticator App
<%= t('force_2fa_with_authenticator_app') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -57,7 +61,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Add signature ID to the documents
<%= t('add_signature_id_to_the_documents') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -69,7 +73,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Require signing reason
<%= t('require_signing_reason') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -81,7 +85,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Allow typed text signatures
<%= t('allow_typed_text_signatures') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -93,7 +97,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Allow to resubmit completed forms
<%= t('allow_to_resubmit_completed_forms') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -105,7 +109,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Remember and pre-fill signatures
<%= t('remember_and_pre_fill_signatures') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -117,7 +121,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Require authentication for file download links
<%= t('require_authentication_for_file_download_links') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -129,7 +133,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Combine completed documents and Audit Log
<%= t('combine_completed_documents_and_audit_log') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -140,8 +144,10 @@
<%= render 'integrations' %>
<% if can?(:manage, current_account) && Docuseal.multitenant? && true_user == current_user %>
<div class="px-1 mt-8">
<h2 class="text-2xl font-bold mb-2">Danger Zone</h2>
<%= button_to button_title(title: 'Delete my account'), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: 'Schedule account for deletion?' }, method: :delete, id: :account_delete_button %>
<h2 class="text-2xl font-bold mb-2">
<%= t('danger_zone') %>
</h2>
<%= button_to button_title(title: t('delete_my_account')), settings_account_path, class: 'btn btn-outline btn-error block', data: { turbo_confirm: t('schedule_account_for_deletion_') }, method: :delete, id: :account_delete_button %>
</div>
<% end %>
</div>
+9 -9
View File
@@ -11,9 +11,9 @@
<div class="flex flex-col md:flex-row gap-4">
<div class="flex w-full space-x-4">
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</div>
<%= button_to button_title(title: 'Rotate', disabled_with: 'Rotate', icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: 'Remove existing API token and generated a new one. Are you sure?' } %>
<%= button_to button_title(title: t('rotate'), disabled_with: t('rotate'), icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: t('remove_existing_api_token_and_generated_a_new_one_are_you_sure_') } %>
</div>
</div>
</div>
@@ -22,7 +22,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div>
Request signature, multiple submitters with default values
<%= t('request_signature_multiple_submitters_with_default_values') %>
</div>
<div class="mt-1">
<div class="badge badge-warning badge-lg">POST</div>
@@ -48,7 +48,7 @@
]
}'<% end.to_str %>
<span class="top-0 right-0 absolute">
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</span>
<pre data-prefix="$"><code class="overflow-hidden w-full"><%= text %></code></pre>
</div>
@@ -58,7 +58,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div>
Request signature, single submitter
<%= t('request_signature_single_submitter') %>
</div>
<div class="mt-1">
<div class="badge badge-warning badge-lg">POST</div>
@@ -74,7 +74,7 @@
"emails": "<%= current_user.email.sub('@', '+test@') %>, <%= current_user.email.sub('@', '+test2@') %>"
}'<% end.to_str %>
<span class="top-0 right-0 absolute">
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</span>
<pre data-prefix="$"><code class="overflow-hidden w-full"><%= text %></code></pre>
</div>
@@ -84,7 +84,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div>
Template details
<%= t('template_details') %>
</div>
<div class="mt-1">
<div class="badge badge-info badge-lg">GET</div>
@@ -96,7 +96,7 @@
<% text = capture do %>curl '<%= api_template_url(current_account.templates&.last || 1) %>' \
--header 'X-Auth-Token: <%= current_user.access_token.token %>'<% end.to_str %>
<span class="top-0 right-0 absolute">
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
<%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</span>
<pre data-prefix="$"><code class="overflow-hidden w-full"><%= text %></code></pre>
</div>
@@ -104,7 +104,7 @@
</div>
</div>
<div class="text-center">
<%= link_to 'Open Full API Reference', "#{Docuseal::PRODUCT_URL}/docs/api", class: 'btn btn-warning text-base mt-4 px-8', target: '_blank', rel: 'noopener' %>
<%= link_to t('open_full_api_reference'), "#{Docuseal::PRODUCT_URL}/docs/api", class: 'btn btn-warning text-base mt-4 px-8', target: '_blank', rel: 'noopener' %>
</div>
</div>
</div>
+2 -2
View File
@@ -1,10 +1,10 @@
<form action="<%= root_path %>" method="get" class="bg-base-200 px-1.5 rounded-xl py-1 whitespace-nowrap">
<toggle-cookies data-value="templates" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="Templates">
<toggle-cookies data-value="templates" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="<%= t('templates') %>">
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9' : 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
<%= svg_icon('layout_grid', class: 'w-6 h-6 stroke-2') %>
</button>
</toggle-cookies>
<toggle-cookies data-value="submissions" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="Submissions">
<toggle-cookies data-value="submissions" data-key="dashboard_view" class="sm:tooltip tooltip-top" data-tip="<%= t('submissions') %>">
<button class="<%= local_assigns[:selected] == 'submissions' ? 'btn btn-neutral !rounded-lg btn-square !p-0 hover:text-neutral-300 !btn-sm !h-8 !w-9' : 'btn !border !rounded-lg btn-square !p-0 !btn-sm !h-8 !w-9 disabled:btn-neutral' %>">
<%= svg_icon('layout_list', class: 'w-6 h-6 stroke-2') %>
</button>
+7 -5
View File
@@ -1,25 +1,27 @@
<div class="max-w-lg mx-auto px-2">
<h1 class="text-4xl font-bold text-center mt-8">Change your password</h1>
<h1 class="text-4xl font-bold text-center mt-8">
<%= t('change_your_password') %>
</h1>
<%= 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">
<%= render 'devise/shared/error_messages', resource: %>
<%= f.hidden_field :reset_password_token %>
<div class="form-control">
<%= f.label :password, 'New password', class: 'label' %>
<%= f.label :password, t('new_password'), class: 'label' %>
<%= f.password_field :password, autofocus: true, autocomplete: 'new-password', class: 'base-input' %>
<% if @minimum_password_length %>
<label class="label">
<span class="label-text">(<%= @minimum_password_length %> characters minimum)</span>
<span class="label-text">(<%= t('minimum_password_length_characters_minimum', minimum_password_length: @minimum_password_length) %>)</span>
</label>
<% end %>
</div>
<div class="form-control">
<%= f.label :password_confirmation, 'Confirm new password', class: 'label' %>
<%= f.label :password_confirmation, t('confirm_new_password'), class: 'label' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'base-input' %>
</div>
</div>
<div class="form-control">
<%= f.button button_title(title: 'Change my password', disabled_with: 'Changing password'), class: 'base-button' %>
<%= f.button button_title(title: t('change_my_password'), disabled_with: t('changing_password')), class: 'base-button' %>
</div>
<% end %>
</div>
+5 -3
View File
@@ -1,15 +1,17 @@
<div class="max-w-lg mx-auto px-2">
<h1 class="text-4xl font-bold text-center mt-8">Forgot your password?</h1>
<h1 class="text-4xl font-bold text-center mt-8">
<%= t('forgot_your_password_') %>
</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'space-y-6' }) do |f| %>
<div class="space-y-2">
<%= render 'devise/shared/error_messages', resource: %>
<div class="form-control">
<%= f.label :email, class: 'label' %>
<%= f.label :email, t(:email), class: 'label' %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'base-input' %>
</div>
</div>
<div class="form-control">
<%= f.button button_title(title: 'Reset password', disabled_with: 'Resetting password'), class: 'base-button' %>
<%= f.button button_title(title: t('reset_password'), disabled_with: t('resetting_password')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -0,0 +1 @@
+5 -3
View File
@@ -1,5 +1,7 @@
<div class="max-w-lg mx-auto px-2">
<h1 class="text-4xl font-bold text-center mt-8">Sign In</h1>
<h1 class="text-4xl font-bold text-center mt-8">
<%= t('sign_in') %>
</h1>
<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :email %>
<%= f.hidden_field :password %>
@@ -8,12 +10,12 @@
<% end %>
<div class="space-y-2">
<div class="form-control">
<%= f.label :otp_attempt, 'Two-Factor Code from Authenticator App', class: 'label' %>
<%= f.label :otp_attempt, t('two_factor_code_from_authenticator_app'), class: 'label' %>
<%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %>
</div>
</div>
<div class="form-control">
<%= f.button button_title(title: 'Sign In', disabled_with: 'Signing In'), class: 'base-button' %>
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
</div>
<% end %>
</div>
+1 -1
View File
@@ -3,7 +3,7 @@
<%= link_to t('already_have_an_account'), new_session_path(resource_name, { lang: params[:lang] }.compact_blank), class: 'link link-hover mx-auto' %>
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t('create_free_account'), registration_path({ redir: params[:redir], lang: params[:lang] }.compact_blank), class: 'link link-hover' %>
<%= link_to t('create_free_account'), registration_path({ redir: params[:redir], lang: params[:lang] }.compact_blank), class: 'link link-hover capitalize' %>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to t('forgot_your_password_'), new_password_path(resource_name, { lang: params[:lang] }.compact_blank), class: 'link link-hover' %>
+8 -8
View File
@@ -7,31 +7,31 @@
<%= f.fields_for :value do |ff| %>
<div class="grid md:grid-cols-2 gap-4">
<div class="form-control">
<%= ff.label :host, class: 'label' %>
<%= ff.label :host, 'Host', class: 'label' %>
<%= ff.text_field :host, value: value['host'], required: true, class: 'base-input' %>
</div>
<div class="form-control">
<%= ff.label :port, class: 'label' %>
<%= ff.label :port, 'Port', class: 'label' %>
<%= ff.text_field :port, value: value['port'], required: true, class: 'base-input' %>
</div>
</div>
<div class="grid md:grid-cols-2 gap-4">
<div class="form-control">
<%= ff.label :username, class: 'label' %>
<%= ff.label :username, 'Username', class: 'label' %>
<%= ff.text_field :username, value: value['username'], class: 'base-input' %>
</div>
<div class="form-control">
<%= ff.label :password, class: 'label' %>
<%= ff.label :password, 'Password', class: 'label' %>
<%= ff.password_field :password, value: value['password'], class: 'base-input' %>
</div>
</div>
<div class="grid md:grid-cols-2 gap-4">
<div class="form-control">
<%= ff.label :domain, 'Domain (optional)', class: 'label' %>
<%= ff.label :domain, "Domain (#{t('optional')})", class: 'label' %>
<%= ff.text_field :domain, value: value['domain'], class: 'base-input' %>
</div>
<div class="form-control">
<%= ff.label :authentication, class: 'label' %>
<%= ff.label :authentication, 'Authentication', class: 'label' %>
<%= ff.select :authentication, options_for_select([%w[Plain plain], %w[Login login], %w[CRAM-MD5 cram_md5]], value.fetch('authentication', 'plain')), { prompt: true }, required: true, class: 'base-select' %>
</div>
</div>
@@ -47,12 +47,12 @@
</div>
</div>
<div class="form-control">
<%= ff.label :from_email, 'Send from Email', class: 'label' %>
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
<%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
+7 -5
View File
@@ -1,19 +1,21 @@
<%= render 'shared/turbo_modal', title: 'Upload Certificate' do %>
<%= render 'shared/turbo_modal', title: t('upload_certificate') do %>
<%= form_for @cert_record, url: settings_esign_path, html: { class: 'space-y-4', enctype: 'multipart/form-data' }, data: { turbo_frame: :_top } do |f| %>
<div class="space-y-2">
<div class="form-control">
<%= f.label :name, class: 'label' %>
<%= f.label :name, t('name'), class: 'label' %>
<%= f.text_field :name, required: true, class: 'base-input' %>
</div>
<div class="form-control">
<%= f.label :file, class: 'label' %>
<%= f.label :file, t('file'), class: 'label' %>
<%= f.file_field :file, required: true %>
<label class="label">
<span class="label-text-alt">Use a valid .der, .p12 or .pfx file.</span>
<span class="label-text-alt">
<%= t('use_a_valid_der_p12_or_pfx_file') %>
</span>
</label>
</div>
<div class="form-control">
<%= f.label :password, 'Password (optional)', class: 'label' %>
<%= f.label :password, "#{t('password')} (#{t('optional')})", class: 'label' %>
<%= f.password_field :password, class: 'base-input' %>
</div>
</div>
+31 -23
View File
@@ -2,17 +2,19 @@
<%= render 'shared/settings_nav' %>
<div class="md:flex-grow">
<div class="max-w-xl">
<h1 class="text-4xl font-bold mb-4">PDF Signature</h1>
<h1 class="text-4xl font-bold mb-4">
<%= t('pdf_signature') %>
</h1>
<div id="result">
<p class="mb-2">
Upload signed PDF file to validate its signature:
<%= t('upload_signed_pdf_file_to_validate_its_signature_') %>
</p>
</div>
<%= form_for '', url: verify_pdf_signature_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %>
<%= f.button type: 'submit', class: 'flex' do %>
<div class="disabled mb-3">
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %>
Analyzing...
<%= "#{t('analyzing')}..." %>
</div>
<% end %>
<file-dropzone data-name="verify_attachments" data-submit-on-upload="true" class="w-full">
@@ -26,10 +28,10 @@
<%= svg_icon('loader', class: 'w-10 h-10 animate-spin') %>
</span>
<div class="font-medium mb-1">
Verify Signed PDF
<%= t('verify_signed_pdf') %>
</div>
<div class="text-xs">
<span class="font-medium">Click to upload</span> or drag and drop files
<%= t('click_to_upload_or_drag_and_drop_files_html') %>
</div>
</div>
<input id="file" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="application/pdf" multiple>
@@ -39,11 +41,13 @@
<% end %>
</div>
<div class="flex justify-between items-end mb-4 mt-8">
<h2 class="text-3xl font-bold">Signing Certificates</h2>
<h2 class="text-3xl font-bold">
<%= t('signing_certificates') %>
</h2>
<% if can?(:create, @encrypted_config) %>
<%= link_to new_settings_esign_path, class: 'btn btn-primary btn-md', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('plus', class: 'w-6 h-6') %>
<span>Upload Cert</span>
<span><%= t('upload_cert') %></span>
<% end %>
<% end %>
</div>
@@ -53,13 +57,13 @@
<thead class="bg-base-200">
<tr class="text-neutral uppercase">
<th>
Name
<%= t('name') %>
</th>
<th>
Valid To
<%= t('valid_to') %>
</th>
<th>
Status
<%= t('status') %>
</th>
<th class="text-right" width="1px">
</th>
@@ -77,18 +81,18 @@
<td>
<% if item['status'] == 'default' %>
<span class="badge badge-lg badge-info badge-outline">
<%= item['status'] %>
<%= t('default') %>
</span>
<% elsif can?(:update, @encrypted_config) %>
<%= button_to settings_esign_path, method: :put, params: { name: item['name'] }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %>
Make Default
<%= button_to settings_esign_path, method: :put, params: { name: item['name'] }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: t('make_default'), data: { turbo_confirm: t('are_you_sure_') } do %>
<%= t('make_default') %>
<% end %>
<% end %>
</td>
<td>
<% if item['name'] != EsignSettingsController::DEFAULT_CERT_NAME && item['status'] != 'default' && can?(:destroy, @encrypted_config) %>
<%= button_to settings_esign_path, params: { name: item['name'] }, method: :delete, class: 'btn btn-outline btn-error btn-xs', title: 'Delete', data: { turbo_confirm: 'Are you sure?' } do %>
Remove
<%= button_to settings_esign_path, params: { name: item['name'] }, method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %>
<%= t('remove') %>
<% end %>
<% end %>
</td>
@@ -101,24 +105,26 @@
<% if !Docuseal.multitenant? && can?(:manage, encrypted_config) %>
<div class="flex-grow max-w-xl">
<div class="flex justify-between items-end mb-4 mt-8">
<h2 class="text-3xl font-bold">Timestamp Server</h2>
<h2 class="text-3xl font-bold">
<%= t('timestamp_server') %>
</h2>
</div>
<%= form_for encrypted_config, url: timestamp_server_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
<div class="form-control">
<%= f.label :value, class: 'label' do %>
<span class="flex items-center space-x-1">
<span>
Timeserver URL
<%= t('timeserver_url') %>
</span>
<span class="tooltip" data-tip="URL of the trusted RFC 3161 timeserver to be used to generate timestamp signatures.">
<span class="tooltip" data-tip="<%= t('url_of_the_trusted_rfc_3161_timeserver_to_be_used_to_generate_timestamp_signatures') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</span>
<% end %>
<%= f.url_field :value, autocomplete: 'off', class: 'base-input', placeholder: 'URL (optional)' %>
<%= f.url_field :value, autocomplete: 'off', class: 'base-input', placeholder: "URL (#{t('optional')})" %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -127,14 +133,16 @@
<% if can?(:manage, account_config) %>
<div class="px-1 mt-8 max-w-xl">
<div class="flex justify-between items-end mb-4 mt-8">
<h2 class="text-3xl font-bold">Preferences</h2>
<h2 class="text-3xl font-bold">
<%= t('preferences') %>
</h2>
</div>
<% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Apply multiple PDF digital signatures in the document per each signer
<%= t('apply_multiple_pdf_digital_signatures_in_the_document_per_each_signer') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value == 'multiple', onchange: 'this.form.requestSubmit()' }, 'multiple', 'single' %>
</div>
@@ -146,7 +154,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Remove PDF form fillable fields from the signed PDF (flatten form)
<%= t('remove_pdf_form_fillable_fields_from_the_signed_pdf_flatten_form') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' } %>
</div>
+5 -5
View File
@@ -1,28 +1,28 @@
<div class="max-w-xl mx-auto px-2">
<h1 class="flex text-4xl font-bold items-center justify-center my-8 space-x-2">
<%= svg_icon('waving_hand', class: 'h-10 w-10') %>
<span>Welcome to <%= Docuseal.product_name %></span>
<span><%= t('welcome_to_product_name', product_name: Docuseal.product_name) %></span>
</h1>
<%= 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">
<%= render 'devise/shared/error_messages', resource: %>
<%= f.hidden_field :reset_password_token %>
<div class="form-control">
<%= f.label :password, 'Set password', class: 'label' %>
<%= f.label :password, t('set_password'), class: 'label' %>
<%= f.password_field :password, autofocus: true, autocomplete: 'new-password', class: 'base-input' %>
<% if @minimum_password_length %>
<label class="label">
<span class="label-text">(<%= @minimum_password_length %> characters minimum)</span>
<span class="label-text">(<%= t('minimum_password_length_characters_minimum', minimum_password_length: @minimum_password_length) %>)</span>
</label>
<% end %>
</div>
<div class="form-control">
<%= f.label :password_confirmation, 'Confirm new password', class: 'label' %>
<%= f.label :password_confirmation, t('confirm_password'), class: 'label' %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'base-input' %>
</div>
</div>
<div class="form-control">
<%= f.button button_title(title: 'Save password and Sign in', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save_password_and_sign_in'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
+2 -2
View File
@@ -1,6 +1,6 @@
<%= form_for '', url: mfa_setup_path, data: { turbo_frame: :_top }, html: { id: 'mfa_form' } do |f| %>
<p class="text-center">
Use an authenticator mobile app like Google Authenticator or 1Password to scan the QR code below.
<%= t('use_an_authenticator_mobile_app_like_google_authenticator_or_1password_to_scan_the_qr_code_below') %>
</p>
<div>
<%== RQRCode::QRCode.new(@provision_url).as_svg(viewbox: true, svg_attributes: { class: 'md:w-80 md:h-80 my-4 mx-auto' }) %>
@@ -12,6 +12,6 @@
</span>
</div>
<div class="form-control mt-4">
<%= f.button button_title(title: 'Save'), class: 'base-button' %>
<%= f.button button_title(title: t('save')), class: 'base-button' %>
</div>
<% end %>
+2 -2
View File
@@ -1,4 +1,4 @@
<%= render 'shared/turbo_modal', title: 'Remove 2FA' do %>
<%= render 'shared/turbo_modal', title: t('remove_2fa') do %>
<%= form_for '', url: mfa_setup_path, method: :delete, data: { turbo_frame: :_top } do |f| %>
<div class="form-control my-6 space-y-2">
<%= f.text_field :otp_attempt, required: true, placeholder: 'XXX-XXX', class: 'base-input text-center' %>
@@ -7,7 +7,7 @@
</span>
</div>
<div class="form-control mt-4">
<%= f.button button_title(title: 'Remove 2FA'), class: 'base-button' %>
<%= f.button button_title(title: t('remove_2fa')), class: 'base-button' %>
</div>
<% end %>
<% end %>
+1 -1
View File
@@ -1,3 +1,3 @@
<%= render 'shared/turbo_modal', title: 'Setup 2FA' do %>
<%= render 'shared/turbo_modal', title: t('setup_2fa') do %>
<%= render 'mfa_setup/form' %>
<% end %>
+3 -1
View File
@@ -1,4 +1,6 @@
<div class="max-w-lg mx-auto px-2">
<h1 class="text-4xl font-bold text-center mt-8">Setup 2FA</h1>
<h1 class="text-4xl font-bold text-center mt-8">
<%= t('setup_2fa') %>
</h1>
<%= render 'mfa_setup/form' %>
</div>
+3 -3
View File
@@ -1,14 +1,14 @@
<div class="max-w-xl mx-auto px-2">
<h1 class="text-4xl font-bold text-center my-8">👨‍💻 Developer Newsletters</h1>
<h1 class="text-4xl font-bold text-center my-8">👨‍💻 <%= t('developer_newsletters') %></h1>
<%= form_for current_user, url: newsletter_path do |f| %>
<div class="form-control">
<%= f.email_field :email, placeholder: 'Email', required: true, class: 'base-input' %>
<%= f.email_field :email, placeholder: t('email'), required: true, class: 'base-input' %>
</div>
<div class="form-control mt-4">
<%= f.button button_title, class: 'base-button' %>
</div>
<div class="text-center mt-2">
<a href="/" class="link">Skip</a>
<a href="/" class="link"><%= t('skip') %></a>
</div>
<% end %>
</div>
@@ -4,7 +4,7 @@
<%= f.label :value, class: 'label' do %>
<span class="flex items-center space-x-1">
<span>
Completed documents notification BCC address
<%= t('completed_documents_notification_bcc_address') %>
</span>
<span class="tooltip" data-tip="Send email copy with completed documents to a specified BCC address.">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
@@ -14,6 +14,6 @@
<%= tag.input type: 'email', multiple: true, name: 'account_config[value]', autocomplete: 'off', class: 'base-input', value: f.object.value %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
@@ -2,29 +2,29 @@
<%= f.hidden_field :key %>
<div class="form-control">
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %>
<% durations = Docuseal.multitenant? ? AccountConfigs::REMINDER_DURATIONS.except('one_hour', 'two_hours').invert : AccountConfigs::REMINDER_DURATIONS.invert %>
<% durations = (Docuseal.multitenant? ? AccountConfigs::REMINDER_DURATIONS.except('one_hour', 'two_hours') : AccountConfigs::REMINDER_DURATIONS).keys.map { |v| [t(v.underscore), v] } %>
<div class="flex flex-col md:flex-row gap-4">
<div class="w-full">
<%= f.fields_for :value, record do |ff| %>
<%= ff.label :first_duration, 'First reminder in', class: 'label' %>
<%= ff.label :first_duration, t('first_reminder_in'), class: 'label' %>
<%= ff.select :first_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %>
</div>
<div class="w-full">
<%= f.fields_for :value, record do |ff| %>
<%= ff.label :second_duration, 'Second reminder in', class: 'label' %>
<%= ff.label :second_duration, t('second_reminder_in'), class: 'label' %>
<%= ff.select :second_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %>
</div>
<div class="w-full">
<%= f.fields_for :value, record do |ff| %>
<%= ff.label :third_duration, 'Third reminder in', class: 'label' %>
<%= ff.label :third_duration, t('third_reminder_in'), class: 'label' %>
<%= ff.select :third_duration, durations, { include_blank: 'None' }, class: 'base-select' %>
<% end %>
</div>
</div>
</div>
<div class="form-control pt-4">
<%= f.button button_title(title: 'Save', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
@@ -1,11 +1,15 @@
<div class="alert my-4">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
<p class="font-bold">Unlock with DocuSeal Pro</p>
<p class="font-bold">
<%= t('unlock_with_docuseal_pro') %>
</p>
<p>
Send automatic email reminders to your recipients.
<%= t('send_automatic_email_reminders_to_your_recipients') %>
<br>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
<%= t('learn_more') %>
</a>
</p>
</div>
</div>
@@ -1,7 +1,9 @@
<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">Email Notifications</h1>
<h1 class="text-4xl font-bold mb-4">
<%= t('email_notifications') %>
</h1>
<div class="mt-2 mb-1">
<% user_config = UserConfig.find_or_initialize_by(user: current_user, key: UserConfig::RECEIVE_COMPLETED_EMAIL) %>
<% if can?(:manage, user_config) %>
@@ -9,7 +11,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between">
<span>
Receive notification emails on completed submission
<%= t('receive_notification_emails_on_completed_submission') %>
</span>
<%= f.check_box :value, class: 'toggle', checked: user_config.value != false, onchange: 'this.form.requestSubmit()' %>
</div>
@@ -18,7 +20,9 @@
</div>
<%= render 'bcc_form', config: @bcc_config %>
<div class="flex justify-between items-end mb-4 mt-8">
<h2 class="text-3xl font-bold">Sign Request Email Reminders</h2>
<h2 class="text-3xl font-bold">
<%= t('sign_request_email_reminders') %>
</h2>
</div>
<%= render 'reminder_banner' %>
<%= render 'reminder_form', config: @reminder_config %>
@@ -1,8 +1,8 @@
<div class="collapse collapse-plus bg-base-200 overflow-visible">
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div class="collapse-title text-xl font-medium capitalize">
<div>
Documents Copy Email
<%= t('documents_copy_email') %>
</div>
</div>
<div class="collapse-content">
@@ -10,13 +10,13 @@
<%= f.hidden_field :key %>
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log')) do |ff| %>
<div class="form-control">
<%= ff.label :subject, class: 'label' %>
<%= ff.label :subject, t('subject'), class: 'label' %>
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control">
<div class="flex items-center">
<%= ff.label :body, class: 'label' %>
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
<%= ff.label :body, t('body'), class: 'label' %>
<span class="tooltip" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</div>
@@ -26,13 +26,13 @@
</div>
<div class="flex items-center justify-between py-2.5 mx-1">
<span>
Attach audit log PDF
<%= t('attach_audit_log_pdf') %>
</span>
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -2,7 +2,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div>
Completed Form Redirect Button
<%= t('completed_form_redirect_button') %>
</div>
</div>
<div class="collapse-content">
@@ -10,16 +10,16 @@
<%= f.hidden_field :key %>
<%= f.fields_for :value, Struct.new(:title, :url).new(*(f.object.value || {}).values_at('title', 'url')) do |ff| %>
<div class="form-control">
<%= ff.label :title, 'Button title', class: 'label' %>
<%= ff.label :title, t('button_title'), class: 'label' %>
<%= ff.text_field :title, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control">
<%= ff.label :url, 'Button URL', class: 'label' %>
<%= ff.label :url, t('button_url'), class: 'label' %>
<%= ff.url_field :url, class: 'base-input' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -2,7 +2,7 @@
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div>
Completed Form Message
<%= t('completed_form_message') %>
</div>
</div>
<div class="collapse-content">
@@ -10,18 +10,18 @@
<%= f.hidden_field :key %>
<%= f.fields_for :value, Struct.new(:title, :body).new(*(f.object.value || {}).values_at('title', 'body')) do |ff| %>
<div class="form-control">
<%= ff.label :title, 'Title', class: 'label' %>
<%= ff.label :title, t('title'), class: 'label' %>
<%= ff.text_field :title, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control">
<%= ff.label :body, 'Body', class: 'label' %>
<%= ff.label :body, t('body'), class: 'label' %>
<autoresize-textarea>
<%= ff.text_area :body, class: 'base-input w-full py-2', dir: 'auto' %>
</autoresize-textarea>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -5,7 +5,7 @@
<%= f.hidden_field :key %>
<div class="flex items-center justify-between py-2.5">
<span>
Show confetti on successful completion
<%= t('show_confetti_on_successful_completion') %>
</span>
<%= f.check_box :value, { class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' }, '1', '0' %>
</div>
@@ -1,11 +1,15 @@
<div class="alert my-4">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
<p class="font-bold">Unlock with DocuSeal Pro</p>
<p class="font-bold">
<%= t('unlock_with_docuseal_pro') %>
</p>
<p>
Display your company name and logo when signing documents.
<%= t('display_your_company_name_and_logo_when_signing_documents') %>
<br>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
<%= t('learn_more') %>
</a>
</p>
</div>
</div>
@@ -1,8 +1,8 @@
<div class="collapse collapse-plus bg-base-200 overflow-visible">
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div class="collapse-title text-xl font-medium capitalize">
<div>
Signature Request Email
<%= t('signature_request_email') %>
</div>
</div>
<div class="collapse-content">
@@ -10,13 +10,13 @@
<%= f.hidden_field :key %>
<%= f.fields_for :value, Struct.new(:subject, :body).new(*f.object.value.values_at('subject', 'body')) do |ff| %>
<div class="form-control">
<%= ff.label :subject, class: 'label' %>
<%= ff.label :subject, t('subject'), class: 'label' %>
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control">
<div class="flex items-center">
<%= ff.label :body, class: 'label' %>
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
<%= ff.label :body, t('body'), class: 'label' %>
<span class="tooltip" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</div>
@@ -26,7 +26,7 @@
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -1,8 +1,8 @@
<div class="collapse collapse-plus bg-base-200 overflow-visible">
<input type="checkbox">
<div class="collapse-title text-xl font-medium">
<div class="collapse-title text-xl font-medium capitalize">
<div>
Completed Notification Email
<%= t('copleted_notification_email') %>
</div>
</div>
<div class="collapse-content">
@@ -11,8 +11,8 @@
<%= f.fields_for :value, Struct.new(:subject, :body, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'attach_audit_log', 'attach_documents')) do |ff| %>
<div class="form-control">
<div class="flex items-center">
<%= ff.label :subject, class: 'label' %>
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY, 'subject').scan(/{{.*?}}/).join(', ') %>">
<%= ff.label :subject, t('subject'), class: 'label' %>
<span class="tooltip" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY].call['subject'].scan(/{.*?}/).join(', ') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</div>
@@ -20,8 +20,8 @@
</div>
<div class="form-control">
<div class="flex items-center">
<%= ff.label :body, class: 'label' %>
<span class="tooltip" data-tip="Use following placeholders text: <%= AccountConfig::DEFAULT_VALUES.dig(AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY, 'body').scan(/{{.*?}}/).join(', ') %>">
<%= ff.label :body, t('body'), class: 'label' %>
<span class="tooltip" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
</span>
</div>
@@ -31,19 +31,19 @@
</div>
<div class="flex items-center justify-between pt-2.5 mx-1">
<span>
Attach documents
<%= t('attach_documents') %>
</span>
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
</div>
<div class="flex items-center justify-between pb-2.5 mx-1">
<span>
Attach audit log PDF
<%= t('attach_audit_log_pdf') %>
</span>
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
</div>
<% end %>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -1,15 +1,21 @@
<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">
<p class="text-4xl font-bold mb-4">Email Templates</p>
<p class="text-4xl font-bold mb-4">
<%= t('email_templates') %>
</p>
<div class="space-y-4">
<%= render 'signature_request_email_form' %>
<%= render 'documents_copy_email_form' %>
<%= render 'submitter_completed_email_form' %>
</div>
<p class="text-4xl font-bold mb-4 mt-8">Company Logo</p>
<p class="text-4xl font-bold mb-4 mt-8">
<%= t('company_logo') %>
</p>
<%= render 'logo_form' %>
<p class="text-4xl font-bold mb-4 mt-8">Submission Form</p>
<p class="text-4xl font-bold mb-4 mt-8">
<%= t('submission_form') %>
</p>
<div class="space-y-4">
<%= render 'form_completed_message_form' %>
<%= render 'form_completed_button_form' %>
+34 -20
View File
@@ -1,78 +1,92 @@
<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">Profile</h1>
<h1 class="text-4xl font-bold mb-4">
<%= t('profile') %>
</h1>
<%= 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="form-control">
<%= f.label :first_name, class: 'label' %>
<%= f.label :first_name, t('first_name'), class: 'label' %>
<%= f.text_field :first_name, required: true, class: 'base-input', dir: 'auto' %>
</div>
<div class="form-control">
<%= f.label :last_name, class: 'label' %>
<%= f.label :last_name, t('last_name'), class: 'label' %>
<%= f.text_field :last_name, required: true, class: 'base-input', dir: 'auto' %>
</div>
</div>
<div class="form-control">
<%= f.label :email, 'Email', class: 'label' %>
<%= f.label :email, t('email'), class: 'label' %>
<%= f.email_field :email, autocomplete: 'off', class: 'base-input' %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('update'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
<%= render 'email_configs' %>
<p class="text-2xl font-bold mt-8 mb-4">Signature</p>
<p class="text-2xl font-bold mt-8 mb-4">
<%= t('signature') %>
</p>
<% signature = UserConfigs.load_signature(current_user) %>
<% if signature %>
<div class="flex justify-center mb-4 relative">
<%= button_to button_title(title: 'Remove', disabled_with: 'Removing'), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %>
<img src="<%= signature.url %>" style="max-height: 200px; width: auto" width="<%= signature.metadata['width'] %>" height="<%= signature.metadata['height'] %>">
</div>
<% end %>
<a href="<%= edit_user_signature_path %>" data-turbo-frame="modal" class="base-button w-full">Update Signature</a>
<p class="text-2xl font-bold mt-8 mb-4">Initials</p>
<a href="<%= edit_user_signature_path %>" data-turbo-frame="modal" class="base-button w-full">
<%= t('update_signature') %>
</a>
<p class="text-2xl font-bold mt-8 mb-4">
<%= t('initials') %>
</p>
<% initials = UserConfigs.load_initials(current_user) %>
<% if initials %>
<div class="flex justify-center mb-4 relative">
<%= button_to button_title(title: 'Remove', disabled_with: 'Removing'), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %>
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %>
<img src="<%= initials.url %>" style="max-height: 200px; width: auto" width="<%= initials.metadata['width'] %>" height="<%= initials.metadata['height'] %>">
</div>
<% end %>
<a href="<%= edit_user_initials_path %>" data-turbo-frame="modal" class="base-button w-full">Update Initials</a>
<a href="<%= edit_user_initials_path %>" data-turbo-frame="modal" class="base-button w-full">
<%= t('update_initials') %>
</a>
<% if true_user == current_user && !current_account.testing? %>
<p class="text-2xl font-bold mt-8 mb-4">Change Password</p>
<p class="text-2xl font-bold mt-8 mb-4">
<%= t('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| %>
<div class="form-control">
<%= f.label :password, 'New password', class: 'label' %>
<%= f.label :password, t('new_password'), class: 'label' %>
<%= f.password_field :password, autocomplete: 'off', class: 'base-input' %>
</div>
<div class="form-control">
<%= f.label :password_confirmation, 'Confirm new password', class: 'label' %>
<%= f.label :password_confirmation, t('confirm_password'), class: 'label' %>
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'base-input' %>
</div>
<div class="form-control pt-2">
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
<%= f.button button_title(title: t('update'), disabled_with: t('updating')), class: 'base-button' %>
</div>
<% end %>
<p class="text-2xl font-bold mt-8 mb-4">Two-Factor Authentication</p>
<p class="text-2xl font-bold mt-8 mb-4">
<%= t('two_factor_authentication') %>
</p>
<div class="space-y-4">
<% if current_user.otp_required_for_login %>
<p class="flex items-center space-x-1">
<%= svg_icon('circle_check', class: 'stroke-success inline flex-none w-5 h-5') %>
<span>
2FA has been configured.
<%= t('2fa_has_been_configured') %>
</span>
</p>
<a href="<%= edit_mfa_setup_path %>" data-turbo-frame="modal" class="white-button w-full !px-8">🔓 Remove 2FA</a>
<a href="<%= edit_mfa_setup_path %>" data-turbo-frame="modal" class="white-button w-full !px-8">🔓 <%= t('remove_2fa') %></a>
<% else %>
<p class="flex items-center space-x-1">
<%= svg_icon('info_circle', class: 'stroke-warning inline flex-none w-5 h-5') %>
<span>
2FA is not configured.
<%= t('2fa_is_not_configured') %>
</span>
</p>
<a href="<%= new_mfa_setup_path %>" data-turbo-frame="modal" class="base-button w-full !px-8">🔒 Set up 2FA</a>
<a href="<%= new_mfa_setup_path %>" data-turbo-frame="modal" class="base-button w-full !px-8">🔒 <%= t('set_up_2fa') %></a>
<% end %>
</div>
<% end %>
@@ -5,7 +5,7 @@
<%= render 'start_form/docuseal_logo' %>
</div>
<div class="text-center text-4xl font-bold">
Email has been sent
<%= t('email_has_been_sent') %>
</div>
</div>
</div>
+7 -7
View File
@@ -1,6 +1,6 @@
<div class="max-w-xl mx-auto px-2">
<h1 class="flex text-4xl font-bold items-center justify-center my-8 space-x-2">
<span>Initial Setup</span>
<span><%= t('initial_setup') %></span>
<%= svg_icon('waving_hand', class: 'h-10 w-10') %>
</h1>
<%= form_for '', html: { class: 'space-y-6' }, url: setup_index_path do |f| %>
@@ -8,18 +8,18 @@
<%= fields_for @user do |ff| %>
<div class="grid gap-2 md:grid-cols-2 md:gap-4">
<div class="form-control">
<%= ff.label :first_name, class: 'label' %>
<%= ff.label :first_name, t('first_name'), class: 'label' %>
<%= ff.text_field :first_name, required: true, class: 'base-input' %>
</div>
<div class="form-control">
<%= ff.label :last_name, class: 'label' %>
<%= ff.label :last_name, t('last_name'), class: 'label' %>
<%= ff.text_field :last_name, required: true, class: 'base-input' %>
</div>
</div>
<% end %>
<%= fields_for @user do |ff| %>
<div class="form-control">
<%= ff.label :email, class: 'label' %>
<%= ff.label :email, t('email'), class: 'label' %>
<%= ff.email_field :email, required: true, class: 'base-input' %>
</div>
<% end %>
@@ -27,13 +27,13 @@
<set-timezone data-input-id="account_timezone"></set-timezone>
<%= ff.hidden_field :timezone %>
<div class="form-control">
<%= ff.label :name, 'Company name', class: 'label' %>
<%= ff.label :name, t('company_name'), class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input' %>
</div>
<% end %>
<%= fields_for @user do |ff| %>
<div class="form-control">
<%= ff.label :password, class: 'label' %>
<%= ff.label :password, t('password'), class: 'label' %>
<password-input class="block relative">
<%= ff.password_field :password, required: true, placeholder: '************', class: 'base-input w-full pr-10', data: { target: 'password-input.passwordInput' } %>
<button data-target="password-input.togglePasswordVisibility" type="button" class="absolute inset-y-0 h-12 right-0 pr-3 flex items-center">
@@ -52,7 +52,7 @@
<% if @encrypted_config.value.blank? %>
<set-origin-url data-input-id="encrypted_config_value"></set-origin-url>
<% end %>
<%= ff.label :value, 'App URL', class: 'label' %>
<%= ff.label :value, t('app_url'), class: 'label' %>
<%= ff.text_field :value, required: true, class: 'base-input' %>
</div>
<% end %>
+2 -3
View File
@@ -1,10 +1,9 @@
<div class="mx-auto mt-1.5">
<div class="py-3 rounded-2xl flex items-center justify-between mx-4 md:mx-0">
<div class="w-full text-center">
<span class="font-bold">Demo Environment</span>
<span class="font-bold"><%= t('demo_environment') %></span>
<br>
<a href="<%= new_template_path %>" data-turbo-frame="modal" class="inline underline font-medium">Create a new template</a> document form or
<a href="<%= start_form_url(slug: ::Template.first&.slug) %>" target="_blank" class="inline underline font-medium">submit the existing one</a> 😊
<%= t('create_a_new_template_document_form_or_submit_the_existing_one_html', new_template_link: new_template_path, start_form_link: start_form_url(slug: ::Template.first&.slug)) %> 😊
</div>
</div>
</div>
+1 -1
View File
@@ -15,5 +15,5 @@
<%= yield %>
</div>
</div>
<label class="modal-backdrop" for="<%= uuid %>">Close</label>
<label class="modal-backdrop" for="<%= uuid %>"><%= t('close') %></label>
</div>
+6 -6
View File
@@ -19,7 +19,7 @@
<% else %>
<div class="flex items-center justify-center space-x-4 mr-1">
<%= render 'shared/navbar_buttons' %>
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
<%= link_to t('settings'), settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
</div>
<% end %>
<div class="dropdown dropdown-end">
@@ -30,14 +30,14 @@
<li>
<%= link_to settings_profile_index_path, class: 'flex items-center' do %>
<%= svg_icon('adjustments', class: 'w-5 h-5 flex-shrink-0 stroke-2') %>
<span class="mr-1">Profile</span>
<span class="mr-1"><%= t('profile') %></span>
<% end %>
</li>
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
<li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, data: { prefetch: false }, class: 'flex items-center' do %>
<%= svg_icon('terminal', class: 'w-5 h-5 stroke-2') %>
Console
<%= t('console') %>
<% end %>
</li>
<% end %>
@@ -45,14 +45,14 @@
<li>
<%= link_to settings_esign_path, class: 'flex items-center' do %>
<%= svg_icon('zoom_check', class: 'w-5 h-5 stroke-2') %>
<span class="mr-1">Verify PDF</span>
<span class="mr-1"><%= t('verify_pdf') %></span>
<% end %>
</li>
<% end %>
<li>
<%= button_to destroy_user_session_path, method: :delete, data: { turbo: false }, class: 'flex items-center' do %>
<%= svg_icon('logout', class: 'w-5 h-5 stroke-2 mr-2 inline') %>
<span class="mr-1">Sign out</span>
<span class="mr-1"><%= t('sign_out') %></span>
<% end %>
</li>
</ul>
@@ -71,7 +71,7 @@
<% if Docuseal.multitenant? && !request.path.in?([registration_path, new_registration_path]) %>
<%= link_to registration_path({ lang: params[:lang] }.compact_blank), class: 'btn btn-neutral btn-sm btn-outline' do %>
<span class="flex items-center justify-center space-x-1">
<span class="hidden sm:block"><%= t('create_free_account') %></span>
<span class="hidden sm:block capitalize"><%= t('create_free_account') %></span>
<span class="md:hidden"><%= t('sign_up') %></span>
</span>
<% end %>
+1 -1
View File
@@ -1,5 +1,5 @@
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
Upgrade
<%= t('upgrade') %>
<% end %>
<% if signed_in? && current_user != true_user %>
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
+1 -1
View File
@@ -12,7 +12,7 @@
<span class="join-item btn btn-disabled !bg-base-200 min-h-full h-10">«</span>
<% end %>
<span class="join-item btn uppercase min-h-full h-10">
Page <%= @pagy.page %>
<%= t('page_number', number: @pagy.page) %>
</span>
<% if @pagy.next %>
<%== link.call(@pagy.next, '»', classes: 'join-item btn min-h-full h-10') %>
+3 -3
View File
@@ -2,12 +2,12 @@
<% if local_assigns[:with_counter] %>
<% count = Submitter.where.not(completed_at: nil).distinct.count(:submission_id) %>
<% if count > 1 %>
<b><%= count %></b> documents signed with
<%= t('count_documents_signed_with_html', count:) %>
<% else %>
Powered by
<%= t('powered_by') %>
<% end %>
<% else %>
Powered by
<%= t('powered_by') %>
<% end %>
<a href="<%= Docuseal::PRODUCT_URL %><%= local_assigns[:link_path] %>" class="underline"><%= Docuseal.product_name %></a> - open source documents software
</div>
+19 -17
View File
@@ -2,23 +2,25 @@
<menu-active>
<ul class="menu px-0">
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">&larr; Back</a></li>
<li class="menu-title py-0 !bg-transparent"><span class="!bg-transparent">Settings</span></li>
<li class="menu-title py-0 !bg-transparent">
<span class="!bg-transparent"><%= t('settings') %></span>
</li>
<li></li>
<li>
<%= link_to 'Profile', settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('profile'), settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
</li>
<li>
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('account'), settings_account_path, class: 'text-base hover:bg-base-300' %>
</li>
<% unless Docuseal.multitenant? %>
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::EMAIL_SMTP_KEY, account: current_account)) && ENV['SMTP_ADDRESS'].blank? && true_user == current_user %>
<li>
<%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('email'), settings_email_index_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::FILES_STORAGE_KEY, account: current_account)) && true_user == current_user %>
<li>
<%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('storage'), settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<% if can?(:read, EncryptedConfig.new(key: 'submitter_invitation_sms', account: current_account)) && true_user == current_user %>
@@ -29,22 +31,22 @@
<% end %>
<% if can?(:read, AccountConfig) %>
<li>
<%= link_to 'Notifications', settings_notifications_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('notifications'), settings_notifications_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::ESIGN_CERTS_KEY, account: current_account)) %>
<li>
<%= link_to 'E-Signature', settings_esign_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('e_signature'), settings_esign_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<% if can?(:read, AccountConfig) %>
<li>
<%= link_to 'Personalization', settings_personalization_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('personalization'), settings_personalization_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<% if can?(:read, User) %>
<li>
<%= link_to 'Users', settings_users_path, class: 'text-base hover:bg-base-300' %>
<%= link_to t('users'), settings_users_path, class: 'text-base hover:bg-base-300' %>
</li>
<% end %>
<%= render 'shared/settings_nav_extra' %>
@@ -63,21 +65,21 @@
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %>
<li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
Plans
<span class="badge badge-warning">New</span>
<%= t('plans') %>
<span class="badge badge-warning"><%= t('new') %></span>
<% end %>
</li>
<% end %>
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
<li>
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/api") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
<% if Docuseal.multitenant? %> API <% else %> Console <% end %>
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
<% end %>
</li>
<% if Docuseal.multitenant? %>
<li>
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
Embedding
<%= t('embedding') %>
<% end %>
</li>
<% end %>
@@ -92,7 +94,7 @@
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex w-full' } do |f| %>
<label class="flex items-center text-base hover:bg-base-300 w-full justify-between" for="testing_toggle">
<span class="mr-2 w-full">
Test Environment
<%= t('test_environment') %>
</span>
<%= f.check_box :testing_toggle, class: 'toggle toggle-sm', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
</label>
@@ -105,7 +107,7 @@
<% if Docuseal.multitenant? || cannot?(:manage, :tenants) %>
<div class="mx-4 border-t border-base-300 hidden md:block">
<div class="text-sm mt-3">
Need help? Ask a question:
<%= t('need_help_ask_a_question_') %>
</div>
<div class="flex mt-3 space-x-3">
<div class="tooltip" data-tip="GitHub">
@@ -113,13 +115,13 @@
<%= svg_icon('brand_github', class: 'w-8 h-8') %>
</a>
</div>
<div class="tooltip" data-tip="Discord Community">
<div class="tooltip" data-tip="<%= t('discord_community') %>">
<a href="<%= Docuseal::DISCORD_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
<%= svg_icon('brand_discord', class: 'w-8 h-8') %>
</a>
</div>
<%= capture do %>
<div class="tooltip" data-tip="AI Assistant">
<div class="tooltip" data-tip="<%= t('ai_assistant') %>">
<a href="<%= Docuseal::CHATGPT_URL %>" target="_blank" class="btn btn-circle btn-primary btn-md">
<%= svg_icon('brand_openai', class: 'w-8 h-8') %>
</a>
+4 -3
View File
@@ -1,17 +1,18 @@
<% if signed_in? && current_user != true_user && current_account.testing? %>
<div class="alert py-1 text-sm font-medium gap-x-2 flex whitespace-nowrap">
<a href="<%= testing_api_settings_path %>" data-turbo-frame="modal" class="link font-semibold flex">
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>Testing Environment
<%= svg_icon('code_circle', class: 'w-5 h-5 mr-1') %>
<span><%= t('testing_environment') %></span>
</a>
<span>
|
</span>
<%= button_to testing_account_path, method: :delete, class: 'inline flex' do %>
<% title = capture do %>
<span class="link">Exit</span>
<span class="link"><%= t('exit') %></span>
<span>&times;</span>
<% end %>
<%= button_title(title:, disabled_with: 'Leave') %>
<%= button_title(title:, disabled_with: t('leave')) %>
<% end %>
</div>
<% end %>
+1 -1
View File
@@ -2,7 +2,7 @@
<%= form_for '', url: testing_account_path, method: current_account.testing? ? :delete : :get, html: { class: 'flex' } do |f| %>
<label class="flex items-center justify-between py-2.5" for="testing_toggle">
<span class="mr-2 text-lg">
Test Environment
<%= t('test_environment') %>
</span>
<%= f.check_box :testing_toggle, class: 'toggle', checked: current_account.testing?, onchange: 'this.form.requestSubmit()' %>
</label>
+7 -3
View File
@@ -1,11 +1,15 @@
<div class="alert">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
<p class="font-bold">Send signature requests via SMS</p>
<p class="font-bold">
<%= t('send_signature_requests_via_sms') %>
</p>
<p class="text-gray-700">
Unlock with DocuSeal Pro
<%= t('unlock_with_docuseal_pro') %>
<br>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
<%= t('learn_more') %>
</a>
</p>
</div>
</div>
+7 -3
View File
@@ -1,11 +1,15 @@
<div class="alert">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
<p class="font-bold">Single Sign On with SAML 2.0</p>
<p class="font-bold">
<%= t('single_sign_on_with_saml_2_0') %>
</p>
<p class="text-gray-700">
Unlock with DocuSeal Pro
<%= t('unlock_with_docuseal_pro') %>
<br>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
<%= t('learn_more') %>
</a>
</p>
</div>
</div>
+4 -2
View File
@@ -11,13 +11,15 @@
</div>
<div>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p dir="auto" class="text-sm"><%= t('signed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %></p>
<p dir="auto" class="text-sm">
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
</p>
</div>
</div>
</div>
</div>
<div dir="auto" class="text-center text-2xl font-semibold">
<%= t('form_has_been_submitted_already') %>
<%= @submitter.with_signature_fields? ? t('document_has_been_signed_already') : t('form_has_been_submitted_already') %>
</div>
<% if Docuseal.multitenant? || Accounts.can_send_emails?(@submitter.account) %>
<toggle-submit class="block">
@@ -22,10 +22,10 @@
</div>
</div>
<div class="form-control">
<%= fff.label :endpoint, 'Endpoint (optional)', class: 'label' %>
<%= fff.label :endpoint, "Endpoint (#{t('optional')})", class: 'label' %>
<%= fff.text_field :endpoint, value: configs['endpoint'], type: :url, class: 'base-input' %>
<label class="label">
<span class="label-text-alt">For AWS S3 compatible APIs like Minio.</span>
<span class="label-text-alt"><%= t('for_aws_s3_compatible_apis_like_minio') %></span>
</label>
</div>
<% end %>
@@ -4,11 +4,13 @@
<div class="alert my-4">
<%= svg_icon('info_circle', class: 'stroke-current flex-shrink-0 w-6 h-6') %>
<div>
<p class="font-bold">Store all files on disk</p>
<p class="font-bold">
<%= t('store_all_files_on_disk') %>
</p>
<p class="text-gray-700">
No configs are needed but make sure your disk is persistent
<%= t('no_configs_are_needed_but_make_sure_your_disk_is_persistent') %>
<br>
<i>(not suitable for Heroku and other PaaS)</i>
<i>(<%= t('not_suitable_for_heroku_and_other_paas') %>)</i>
</p>
</div>
</div>
+4 -2
View File
@@ -1,7 +1,9 @@
<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">Storage</h1>
<h1 class="text-4xl font-bold mb-4">
<%= t('storage') %>
</h1>
<% value = @encrypted_config.value || { 'service' => 'disk' } %>
<% configs = value['configs'] || {} %>
<%= form_for @encrypted_config, url: settings_storage_index_path, method: :post, html: { autocomplete: 'off', class: 'w-full' } do |f| %>
@@ -29,7 +31,7 @@
<%= render 'azure_form', f:, configs:, value: %>
</disable-hidden>
<div class="form-control">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
</div>
<% end %>
</div>
@@ -1,11 +1,15 @@
<div class="alert">
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
<div>
<p class="font-bold">Bulk send from Excel XLSX or CSV</p>
<p class="font-bold">
<%= t('bulk_send_from_excel_xlsx_or_csv') %>
</p>
<p class="text-gray-700">
Unlock with DocuSeal Pro
<%= t('unlock_with_docuseal_pro') %>
<br>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">Learn More</a>
<a class="link font-medium" target="_blank" href="<%= Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premise" }.to_query}" %>" data-turbo="false">
<%= t('learn_more') %>
</a>
</p>
</div>
</div>
@@ -44,7 +44,7 @@
<% if params[:selfsign].blank? %>
<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 stroke-2') %>
<span>Add New</span>
<span><%= t('add_new') %></span>
</a>
<% end %>
</dynamic-list>
@@ -54,6 +54,6 @@
<%= render 'send_sms', f: %>
</div>
<div class="form-control">
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
</div>
<% end %>
+4 -4
View File
@@ -5,7 +5,7 @@
<emails-textarea data-bulk-enabled="<%= Docuseal.demo? || !Docuseal.multitenant? || can?(:manage, :bulk_send) %>" data-limit="<%= Docuseal.multitenant? ? (can?(:manage, :bulk_send) ? 40 : 1) : nil %>">
<submitters-autocomplete data-field="email" class="block relative">
<autoresize-textarea>
<%= f.text_area :emails, required: true, class: 'base-textarea w-full !text-lg', placeholder: 'Type emails here...', rows: 2 %>
<%= f.text_area :emails, required: true, class: 'base-textarea w-full !text-lg', placeholder: "#{t('type_emails_here')}...", rows: 2 %>
</autoresize-textarea>
</submitters-autocomplete>
</emails-textarea>
@@ -29,7 +29,7 @@
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>">
<submitters-autocomplete data-field="email">
<linked-input data-target-id="<%= "email_#{item['linked_to_uuid']}" if item['linked_to_uuid'].present? %>">
<%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input-slim w-full', placeholder: 'Email', required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %>
<%= tag.input type: 'email', multiple: true, name: 'submission[1][submitters][][email]', autocomplete: 'off', class: 'base-input-slim w-full', placeholder: t('email'), required: index.zero?, value: item['email'].presence || ((params[:selfsign] && index.zero?) || item['is_requester'] ? current_user.email : ''), id: "email_#{item['uuid']}" %>
</linked-input>
</submitters-autocomplete>
</submitter-item>
@@ -41,7 +41,7 @@
<% if params[:selfsign].blank? %>
<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 stroke-2') %>
<span>Add New</span>
<span><%= t('add_new') %></span>
</a>
<% end %>
</dynamic-list>
@@ -52,7 +52,7 @@
</div>
<div class="form-control">
<%= content_for(:submit_button) || capture do %>
<%= f.button button_title(title: 'Add Recipients'), class: 'base-button' %>
<%= f.button button_title(title: t('add_recipients')), class: 'base-button' %>
<% end %>
</div>
<% end %>
+7 -3
View File
@@ -2,12 +2,16 @@
<div class="">
<input id="api_key" type="text" value="<%= start_form_url(slug: template.slug) %>" class="input font-mono input-bordered w-full bg-white" autocomplete="off" readonly>
<div class="mt-4">
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: template.slug), class: 'base-button w-full block', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %>
<%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: template.slug), class: 'base-button w-full block', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
</div>
</div>
<div class="divider uppercase normal-case mt-5 mb-6 text-neutral-700">Or embed on your website</div>
<div class="divider uppercase normal-case mt-5 mb-6 text-neutral-700">
<%= t('or_embed_on_your_website') %>
</div>
<%= render 'templates/embedding', template: %>
<div class="mt-4">
<a href="#" class="link text-center block" data-action="click:turbo-modal#close">Close</a>
<a href="#" class="link text-center block" data-action="click:turbo-modal#close">
<%= t('close') %>
</a>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More