diff --git a/app/controllers/submissions_dashboard_controller.rb b/app/controllers/submissions_dashboard_controller.rb index 57fb1e47..784df4fd 100644 --- a/app/controllers/submissions_dashboard_controller.rb +++ b/app/controllers/submissions_dashboard_controller.rb @@ -15,6 +15,6 @@ class SubmissionsDashboardController < ApplicationController @submissions = @submissions.pending if params[:status] == 'pending' @submissions = @submissions.completed if params[:status] == 'completed' - @pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc)) + @pagy, @submissions = pagy(@submissions.preload(submitters: :submission_events).order(id: :desc)) end end diff --git a/app/controllers/submitters_controller.rb b/app/controllers/submitters_controller.rb new file mode 100644 index 00000000..225f473a --- /dev/null +++ b/app/controllers/submitters_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class SubmittersController < ApplicationController + load_and_authorize_resource :submitter, only: %i[edit update] + + def edit + @submitter_email_message = + if @submitter.preferences['email_message_uuid'].present? + @submitter.account + .email_messages + .find_by(uuid: @submitter.preferences['email_message_uuid']) + end + end + + def update + submission = @submitter.submission + + if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired? + return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated') + end + + if submitter_params.values.all?(&:blank?) + return redirect_back fallback_location: submission_path(submission), + alert: I18n.t('at_least_one_field_must_be_filled') + end + + if params[:is_custom_message] != '1' + params.delete(:subject) + params.delete(:body) + end + + assign_preferences(@submitter, params) + assign_submitter_attrs(@submitter, submitter_params) + + if @submitter.save + if @submitter.preferences['send_email'] || @submitter.preferences['send_sms'] + Submitters.send_signature_requests([@submitter]) + end + + redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved') + else + redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save') + end + end + + private + + def assign_submitter_attrs(submitter, attrs) + submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone) + + submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email) + + submitter.name = attrs[:name] if attrs.key?(:name) + + submitter + end + + def assign_preferences(submitter, attrs) + submitter_preferences = Submitters.normalize_preferences(submitter.account, current_user, attrs) + + if submitter_preferences.key?('send_email') + submitter.preferences['send_email'] = submitter_preferences['send_email'] + end + + submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms') + + if submitter_preferences.key?('email_message_uuid') + submitter.preferences['email_message_uuid'] = submitter_preferences['email_message_uuid'] + end + + submitter + end + + def submitter_params + params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip) + end +end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index d2f2c9c1..2705fa94 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -15,7 +15,7 @@ class TemplatesController < ApplicationController submissions = submissions.pending if params[:status] == 'pending' submissions = submissions.completed if params[:status] == 'completed' - @pagy, @submissions = pagy(submissions.preload(:submitters).order(id: :desc)) + @pagy, @submissions = pagy(submissions.preload(submitters: :submission_events).order(id: :desc)) rescue ActiveRecord::RecordNotFound redirect_to root_path end diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 329afca4..e841a815 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -31,7 +31,7 @@ "> - " id="detailed_phone_<%= item['uuid'] %>"> + " id="detailed_phone_<%= item['uuid'] %>"> diff --git a/app/views/submissions/_phone_form.html.erb b/app/views/submissions/_phone_form.html.erb index 9c223b69..68bcb8da 100644 --- a/app/views/submissions/_phone_form.html.erb +++ b/app/views/submissions/_phone_form.html.erb @@ -21,7 +21,7 @@ "> - <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('Use internatioanl format: +1xxx...') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %> + <%= tag.input type: 'tel', pattern: '^\+[0-9\s\-]+$', oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')", name: 'submission[1][submitters][][phone]', autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: t('phone'), required: index.zero?, id: "phone_phone_#{item['uuid']}" %> <% if submitters.size > 1 %> diff --git a/app/views/submissions/_send_email.html.erb b/app/views/submissions/_send_email.html.erb index 2b809c28..b9638b49 100644 --- a/app/views/submissions/_send_email.html.erb +++ b/app/views/submissions/_send_email.html.erb @@ -2,12 +2,12 @@ <% can_send_emails = Accounts.can_send_emails?(current_account) %>
<%= f.label :send_email, for: uuid = SecureRandom.uuid, class: 'flex items-center cursor-pointer' do %> - <%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails, checked: can_send_emails %> - <%= t('send_emails') %> + <%= f.check_box :send_email, id: uuid, class: 'base-checkbox', disabled: !can_send_emails, checked: can_send_emails && !local_assigns.key?(:resend_email) %> + <%= local_assigns[:resend_email] ? t('re_send_email') : t('send_email') %> <% end %>
<% if can_send_emails %> - <%= render 'email_stats' %> + <%= render 'submissions/email_stats' %> <%= content_for(:edit_button) || capture do %>
diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 0ecd7adb..d5a22ca7 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -107,12 +107,21 @@ <% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %> <% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
-
-
- - - <%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %> - +
+
+
+ + + <%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %> + +
+ <% if can?(:update, submitter) && !submitter.submission_events.exists?(event_type: 'start_form') && !@submission.archived_at? && !@submission.expired? %> + + <%= link_to edit_submitter_path(submitter), class: 'shrink-0 text-neutral-600 md:text-neutral-400 md:group-hover:text-neutral-600', data: { turbo_frame: 'modal' } do %> + <%= svg_icon('pencil', class: 'w-5 h-5 stroke-2') %> + <% end %> + + <% end %>
<% if submitter&.name.present? %>
diff --git a/app/views/submitters/edit.html.erb b/app/views/submitters/edit.html.erb new file mode 100644 index 00000000..c4d58b36 --- /dev/null +++ b/app/views/submitters/edit.html.erb @@ -0,0 +1,29 @@ +<%= render 'shared/turbo_modal_large', title: t('edit_recipient') do %> +
+ <%= form_for '', url: submitter_path(@submitter), method: :patch, html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> +
+ + + <%= text_field_tag 'submitter[name]', @submitter.name, autocomplete: 'off', class: 'base-input !h-10 w-full', placeholder: "#{t('name')} (#{t('optional')})", dir: 'auto' %> + +
+ + <%= email_field_tag 'submitter[email]', @submitter.email, autocomplete: 'off', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('email')} (#{t('optional')})" %> + + + <%= telephone_field_tag 'submitter[phone]', @submitter.phone, autocomplete: 'off', pattern: '^\+[0-9\s\-]+$', class: 'base-input !h-10 mt-1.5 w-full', placeholder: "#{t('phone')} (#{t('optional')})", oninvalid: "this.value ? this.setCustomValidity('#{t('use_international_format_1xxx_')}') : ''", oninput: "this.setCustomValidity('')" %> + +
+
+
+
+ <%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true %> + <%= render 'submissions/send_sms', f:, resend_sms: @submitter.sent_at? %> +
+
+ <%= f.button button_title(title: t('update_recipient'), disabled_with: t('updating')), class: 'base-button' %> +
+ <% end %> +
+ <%= content_for(:modal_extra) %> +<% end %> diff --git a/app/views/templates/_submission.html.erb b/app/views/templates/_submission.html.erb index edbd2353..4ac672e5 100644 --- a/app/views/templates/_submission.html.erb +++ b/app/views/templates/_submission.html.erb @@ -26,7 +26,8 @@
<% end %> - +
+ <% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %> <% is_submission_completed = submitters.all?(&:completed_at?) && submitters.size.positive? %> <% if submitters.size == 1 %> @@ -47,13 +48,22 @@
<% end %> - - <%= submitter.name || submitter.email || submitter.phone %> + + + <%= submitter.name || submitter.email || submitter.phone %> + + <% if can?(:update, submitter) && !submitter.submission_events.any? { |e| e.event_type == 'start_form' } && !submission.archived_at? && !submission.expired? %> + + <%= link_to edit_submitter_path(submitter), class: 'text-neutral-600 shrink-0', data: { turbo_frame: 'modal' } do %> + <%= svg_icon('pencil', class: 'w-5 h-5 stroke-2') %> + <% end %> + + <% end %>
-
+
<% if submitter.completed_at? %>
- + + + <% if t('sign_now').length < 12 %> + <%= svg_icon('writing_sign', class: 'w-4 h-4 stroke-2') %> + <% end %> + <%= t('sign_now') %> + + <% else %>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: submitter.slug), class: 'btn btn-sm btn-neutral text-white md:w-36 flex', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_link').length < 10 ? t('copy_link') : t('copy'), copy_title_md: t('copy'), copied_title_md: t('copied') %> @@ -88,7 +96,9 @@ <% end %> <% end %>
- <%= t('view') %> + + <%= t('view') %> +
<% if !submission.archived_at? && !template.archived_at? && can?(:destroy, submission) %> @@ -129,8 +139,17 @@
<% end %> - - <%= submitter.name || submitter.email || submitter.phone %> + + + <%= submitter.name || submitter.email || submitter.phone %> + + <% if can?(:update, submitter) && !submitter.submission_events.any? { |e| e.event_type == 'start_form' } && !submission.archived_at? && !submission.expired? %> + + <%= link_to edit_submitter_path(submitter), class: 'text-neutral-600 shrink-0', data: { turbo_frame: 'modal' } do %> + <%= svg_icon('pencil', class: 'w-5 h-5 stroke-2') %> + <% end %> + + <% end %> <% if submitter.completed_at? && !is_submission_completed %> @@ -171,7 +190,7 @@
-
+
<% if is_submission_completed %> <% latest_submitter = submitters.select(&:completed_at?).max_by(&:completed_at) %>
@@ -192,7 +211,9 @@
<% end %>
- <%= t('view') %> + + <%= t('view') %> +
<% if !submission.archived_at? && !template.archived_at? %> @@ -206,5 +227,5 @@ <% end %>
<% end %> - +
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 360e6f6e..1cf346cf 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -613,6 +613,11 @@ en: &en unverified: Unverified document: Document completed_at: Completed At + edit_recipient: Edit Recipient + update_recipient: Update Recipient + use_international_format_1xxx_: 'Use internatioanl format: +1xxx...' + submitter_cannot_be_updated: Submitter cannot be updated. + at_least_one_field_must_be_filled: At least one field must be filled. submission_event_names: send_email_to_html: 'Email sent to %{submitter_name}' send_reminder_email_to_html: 'Reminder email sent to %{submitter_name}' @@ -1244,6 +1249,11 @@ es: &es unverified: No verificado document: Documento completed_at: Completado el + edit_recipient: Editar Destinatario + update_recipient: Actualizar Destinatario + use_international_format_1xxx_: 'Usa el formato internacional: +1xxx...' + submitter_cannot_be_updated: El remitente no puede ser actualizado. + at_least_one_field_must_be_filled: Al menos un campo debe estar completo. submission_event_names: send_email_to_html: 'Correo electrónico enviado a %{submitter_name}' send_reminder_email_to_html: 'Correo de recordatorio enviado a %{submitter_name}' @@ -1875,6 +1885,11 @@ it: &it unverified: Non verificato document: Documento completed_at: Completato il + edit_recipient: Modifica Destinatario + update_recipient: Aggiorna Destinatario + use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...' + submitter_cannot_be_updated: Il mittente non può essere aggiornato. + at_least_one_field_must_be_filled: Almeno un campo deve essere compilato. submission_event_names: send_email_to_html: 'E-mail inviato a %{submitter_name}' send_reminder_email_to_html: 'E-mail di promemoria inviato a %{submitter_name}' @@ -2507,6 +2522,11 @@ fr: &fr unverified: Non vérifié document: Document completed_at: Terminé le + edit_recipient: Modifica Destinatario + update_recipient: Aggiorna Destinatario + use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...' + submitter_cannot_be_updated: Il mittente non può essere aggiornato. + at_least_one_field_must_be_filled: Almeno un campo deve essere compilato. submission_event_names: send_email_to_html: 'E-mail envoyé à %{submitter_name}' send_reminder_email_to_html: 'E-mail de rappel envoyé à %{submitter_name}' @@ -3138,6 +3158,11 @@ pt: &pt unverified: Não verificado document: Documento completed_at: Concluído em + edit_recipient: Editar Destinatário + update_recipient: Atualizar Destinatário + use_international_format_1xxx_: 'Use o formato internacional: +1xxx...' + submitter_cannot_be_updated: O remetente não pode ser atualizado. + at_least_one_field_must_be_filled: Pelo menos um campo deve ser preenchido. submission_event_names: send_email_to_html: 'E-mail enviado para %{submitter_name}' send_reminder_email_to_html: 'E-mail de lembrete enviado para %{submitter_name}' @@ -3769,6 +3794,11 @@ de: &de unverified: Nicht verifiziert document: Dokument completed_at: Abgeschlossen am + edit_recipient: Empfänger bearbeiten + update_recipient: Empfänger aktualisieren + use_international_format_1xxx_: 'Verwenden Sie das internationale Format: +1xxx...' + submitter_cannot_be_updated: Der Absender kann nicht aktualisiert werden. + at_least_one_field_must_be_filled: Mindestens ein Feld muss ausgefüllt werden. submission_event_names: send_email_to_html: 'E-Mail gesendet an %{submitter_name}' send_reminder_email_to_html: 'Erinnerungs-E-Mail gesendet an %{submitter_name}' diff --git a/config/routes.rb b/config/routes.rb index de403274..a9e383cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,7 @@ Rails.application.routes.draw do resources :submissions_archived, only: %i[index], path: 'submissions/archived' resources :submissions, only: %i[index], controller: 'submissions_dashboard' resources :submissions, only: %i[show destroy] + resources :submitters, only: %i[edit update] resources :console_redirect, only: %i[index] resources :upgrade, only: %i[index], controller: 'console_redirect' resources :manage, only: %i[index], controller: 'console_redirect'