From e77b5fa2c9ab572984e8649e6785aee7cb8c8e10 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Mon, 26 May 2025 13:32:32 +0300 Subject: [PATCH] add optional shared link to templates --- app/controllers/api/templates_controller.rb | 1 + app/controllers/start_form_controller.rb | 20 ++++-- .../templates_share_link_controller.rb | 21 ++++++ app/javascript/application.js | 2 + app/javascript/elements/check_on_click.js | 14 ++++ app/javascript/elements/clipboard_copy.js | 2 - app/models/template.rb | 1 + app/views/start_form/private.html.erb | 28 ++++++++ app/views/templates/_title.html.erb | 7 +- app/views/templates_preferences/show.html.erb | 14 +++- app/views/templates_share_link/show.html.erb | 16 +++++ config/locales/i18n.yml | 18 +++++ config/routes.rb | 1 + ...0523121121_add_shared_link_to_templates.rb | 22 ++++++ db/schema.rb | 1 + lib/templates/clone.rb | 1 + lib/templates/serialize_for_api.rb | 2 +- spec/fixtures/fieldtags.docx | Bin 0 -> 8860 bytes spec/requests/templates_spec.rb | 33 +++++++-- spec/system/signing_form_spec.rb | 8 ++- spec/system/template_share_link_spec.rb | 66 ++++++++++++++++++ 21 files changed, 258 insertions(+), 20 deletions(-) create mode 100644 app/controllers/templates_share_link_controller.rb create mode 100644 app/javascript/elements/check_on_click.js create mode 100644 app/views/start_form/private.html.erb create mode 100644 app/views/templates_share_link/show.html.erb create mode 100644 db/migrate/20250523121121_add_shared_link_to_templates.rb create mode 100644 spec/fixtures/fieldtags.docx create mode 100644 spec/system/template_share_link_spec.rb diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 5e8da098..25c0c537 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -100,6 +100,7 @@ module Api permitted_params = [ :name, :external_id, + :shared_link, { submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]], fields: [[:uuid, :submitter_uuid, :name, :type, diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 28a77f86..c7b74a3f 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -11,14 +11,22 @@ class StartFormController < ApplicationController before_action :load_template def show - raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] == true + raise ActionController::RoutingError, I18n.t('not_found') if @template.preferences['require_phone_2fa'] - @submitter = @template.submissions.new(account_id: @template.account_id) - .submitters.new(account_id: @template.account_id, - uuid: (filter_undefined_submitters(@template).first || - @template.submitters.first)['uuid']) + if @template.shared_link? + @submitter = @template.submissions.new(account_id: @template.account_id) + .submitters.new(account_id: @template.account_id, + uuid: (filter_undefined_submitters(@template).first || + @template.submitters.first)['uuid']) - @form_configs = Submitters::FormConfigs.call(@submitter) unless Docuseal.multitenant? + @form_configs = Submitters::FormConfigs.call(@submitter) unless Docuseal.multitenant? + + render :show + elsif current_user && current_ability.can?(:read, @template) + render :private + else + raise ActionController::RoutingError, I18n.t('not_found') + end end def update diff --git a/app/controllers/templates_share_link_controller.rb b/app/controllers/templates_share_link_controller.rb new file mode 100644 index 00000000..5b84f6ca --- /dev/null +++ b/app/controllers/templates_share_link_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class TemplatesShareLinkController < ApplicationController + load_and_authorize_resource :template + + def show; end + + def create + authorize!(:update, @template) + + @template.update!(template_params) + + head :ok + end + + private + + def template_params + params.require(:template).permit(:shared_link) + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index c2e12079..f951b8fe 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -24,6 +24,7 @@ import SubmitForm from './elements/submit_form' import PromptPassword from './elements/prompt_password' import EmailsTextarea from './elements/emails_textarea' import ToggleOnSubmit from './elements/toggle_on_submit' +import CheckOnClick from './elements/check_on_click' import PasswordInput from './elements/password_input' import SearchInput from './elements/search_input' import ToggleAttribute from './elements/toggle_attribute' @@ -103,6 +104,7 @@ safeRegisterElement('set-date-button', SetDateButton) safeRegisterElement('indeterminate-checkbox', IndeterminateCheckbox) safeRegisterElement('app-tour', AppTour) safeRegisterElement('dashboard-dropzone', DashboardDropzone) +safeRegisterElement('check-on-click', CheckOnClick) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/check_on_click.js b/app/javascript/elements/check_on_click.js new file mode 100644 index 00000000..8b3b9ab8 --- /dev/null +++ b/app/javascript/elements/check_on_click.js @@ -0,0 +1,14 @@ +export default class extends HTMLElement { + connectedCallback () { + this.addEventListener('click', () => { + if (!this.element.checked) { + this.element.checked = true + this.element.dispatchEvent(new Event('change', { bubbles: true })) + } + }) + } + + get element () { + return document.getElementById(this.dataset.elementId) + } +} diff --git a/app/javascript/elements/clipboard_copy.js b/app/javascript/elements/clipboard_copy.js index 777727d1..4118b9e2 100644 --- a/app/javascript/elements/clipboard_copy.js +++ b/app/javascript/elements/clipboard_copy.js @@ -3,8 +3,6 @@ export default class extends HTMLElement { this.clearChecked() this.addEventListener('click', (e) => { - e.stopPropagation() - const text = this.dataset.text || this.innerText.trim() if (navigator.clipboard) { diff --git a/app/models/template.rb b/app/models/template.rb index 28441a9a..faa9e696 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -10,6 +10,7 @@ # name :string not null # preferences :text not null # schema :text not null +# shared_link :boolean default(FALSE), not null # slug :string not null # source :text not null # submitters :text not null diff --git a/app/views/start_form/private.html.erb b/app/views/start_form/private.html.erb new file mode 100644 index 00000000..4e16eba4 --- /dev/null +++ b/app/views/start_form/private.html.erb @@ -0,0 +1,28 @@ +<% content_for(:html_title, "#{@template.name} | DocuSeal") %> +<% content_for(:html_description, t('share_link_is_currently_disabled')) %> +
+
+
+
+ <%= render 'banner' %> +

+ <%= t('share_link_is_currently_disabled') %> +

+
+
+
+
+ <%= svg_icon('writing_sign', class: 'w-10 h-10') %> +
+
+

<%= @template.name %>

+ <% if @template.archived_at? %> +

<%= t('form_has_been_deleted_by_html', name: @template.account.name) %>

+ <% end %> +
+
+
+
+
+
+<%= render 'shared/attribution', link_path: '/start', account: @template.account %> diff --git a/app/views/templates/_title.html.erb b/app/views/templates/_title.html.erb index c33a8298..28d2e037 100644 --- a/app/views/templates/_title.html.erb +++ b/app/views/templates/_title.html.erb @@ -49,7 +49,12 @@ <% end %> <% end %> - <%= render 'shared/clipboard_copy', text: start_form_url(slug: @template.slug), id: 'share_link_clipboard', class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', icon_class: 'w-4 h-4 md:w-6 md:h-6 text-white', copy_title: t('link'), copied_title: t('copied'), copy_title_md: t('link'), copied_title_md: t('copied') %> + <%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %> + + <%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6 text-white') %> + <%= t('link') %> + + <% end %> <% end %> diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb index b59fac43..47b342c7 100644 --- a/app/views/templates_preferences/show.html.erb +++ b/app/views/templates_preferences/show.html.erb @@ -380,8 +380,18 @@ <%= t('embedding_url') %>
- - <%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %> + <%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'w-full mt-1' }, data: { close_on_submit: false } do |f| %> +
+ + + <%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %> + +
+
+ <%= t('enable_shared_link') %> + <%= f.check_box :shared_link, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %> +
+ <% end %>
<%= render 'templates_code_modal/placeholder' %> diff --git a/app/views/templates_share_link/show.html.erb b/app/views/templates_share_link/show.html.erb new file mode 100644 index 00000000..eb2d74e0 --- /dev/null +++ b/app/views/templates_share_link/show.html.erb @@ -0,0 +1,16 @@ +<%= render 'shared/turbo_modal_large', title: t('share_link') do %> +
+ <%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %> +
+ <%= t('enable_shared_link') %> + <%= f.check_box :shared_link, { disabled: !can?(:update, @template), class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %> +
+
+ + + <%= render 'shared/clipboard_copy', icon: 'copy', text: start_form_url(slug: @template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %> + +
+ <% end %> +
+<% end %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 5dd85c32..378a6f5e 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -746,6 +746,9 @@ en: &en eu_data_residency: EU data residency please_enter_your_email_address_associated_with_the_completed_submission: Please enter your email address associated with the completed submission. esignature_disclosure: eSignature Disclosure + share_link: Share link + enable_shared_link: Enable shared link + share_link_is_currently_disabled: Share link is currently disabled submission_sources: api: API bulk: Bulk Send @@ -1575,6 +1578,9 @@ es: &es eu_data_residency: Datos alojados UE please_enter_your_email_address_associated_with_the_completed_submission: Por favor, introduce tu dirección de correo electrónico asociada con el envío completado. esignature_disclosure: Uso de firma electrónica + share_link: Enlace para compartir + enable_shared_link: Habilitar enlace compartido + share_link_is_currently_disabled: El enlace compartido está deshabilitado actualmente submission_sources: api: API bulk: Envío masivo @@ -2402,6 +2408,9 @@ it: &it eu_data_residency: "Dati nell'UE" please_enter_your_email_address_associated_with_the_completed_submission: "Inserisci il tuo indirizzo email associato all'invio completato." esignature_disclosure: Uso della firma elettronica + share_link: Link di condivisione + enable_shared_link: Abilita link condiviso + share_link_is_currently_disabled: Il link condiviso è attualmente disabilitato submission_sources: api: API bulk: Invio massivo @@ -3232,6 +3241,9 @@ fr: &fr eu_data_residency: "Données dans l'UE" please_enter_your_email_address_associated_with_the_completed_submission: "Veuillez saisir l'adresse e-mail associée à l'envoi complété." esignature_disclosure: Divulgation de Signature Électronique + share_link: Lien de partage + enable_shared_link: Activer le lien de partage + share_link_is_currently_disabled: Le lien de partage est actuellement désactivé submission_sources: api: API bulk: Envoi en masse @@ -4061,6 +4073,9 @@ pt: &pt eu_data_residency: Dados na UE please_enter_your_email_address_associated_with_the_completed_submission: Por favor, insira seu e-mail associado ao envio concluído. esignature_disclosure: Uso de assinatura eletrônica + share_link: Link de compartilhamento + enable_shared_link: Ativar link compartilhado + share_link_is_currently_disabled: O link compartilhado está desativado no momento submission_sources: api: API bulk: Envio em massa @@ -4891,6 +4906,9 @@ de: &de eu_data_residency: EU-Datenspeicher please_enter_your_email_address_associated_with_the_completed_submission: Bitte gib deine E-Mail-Adresse ein, die mit der abgeschlossenen Übermittlung verknüpft ist. esignature_disclosure: Nutzung der E-Signatur + share_link: Freigabelink + enable_shared_link: 'Freigabelink aktivieren' + share_link_is_currently_disabled: 'Freigabelink ist derzeit deaktiviert' submission_sources: api: API bulk: Massenversand diff --git a/config/routes.rb b/config/routes.rb index 331ca82d..3409a78e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -106,6 +106,7 @@ Rails.application.routes.draw do resource :form, only: %i[show], controller: 'templates_form_preview' resource :code_modal, only: %i[show], controller: 'templates_code_modal' resource :preferences, only: %i[show create], controller: 'templates_preferences' + resource :share_link, only: %i[show create], controller: 'templates_share_link' resources :recipients, only: %i[create], controller: 'templates_recipients' resources :submissions_export, only: %i[index new] end diff --git a/db/migrate/20250523121121_add_shared_link_to_templates.rb b/db/migrate/20250523121121_add_shared_link_to_templates.rb new file mode 100644 index 00000000..6fe962eb --- /dev/null +++ b/db/migrate/20250523121121_add_shared_link_to_templates.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0] + disable_ddl_transaction + + class MigrationTemplate < ActiveRecord::Base + self.table_name = 'templates' + end + + def up + add_column :templates, :shared_link, :boolean, if_not_exists: true + + MigrationTemplate.where(shared_link: nil).in_batches.update_all(shared_link: true) + + change_column_default :templates, :shared_link, from: nil, to: false + change_column_null :templates, :shared_link, false + end + + def down + remove_column :templates, :shared_link + end +end diff --git a/db/schema.rb b/db/schema.rb index 28f1a524..b024ff66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -361,6 +361,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do t.bigint "folder_id", null: false t.string "external_id" t.text "preferences", null: false + t.boolean "shared_link", default: false, null: false t.index ["account_id"], name: "index_templates_on_account_id" t.index ["author_id"], name: "index_templates_on_author_id" t.index ["external_id"], name: "index_templates_on_external_id" diff --git a/lib/templates/clone.rb b/lib/templates/clone.rb index 0fe3b91b..e4b5fc60 100644 --- a/lib/templates/clone.rb +++ b/lib/templates/clone.rb @@ -9,6 +9,7 @@ module Templates template = original_template.account.templates.new template.external_id = external_id + template.shared_link = original_template.shared_link template.author = author template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})" diff --git a/lib/templates/serialize_for_api.rb b/lib/templates/serialize_for_api.rb index f5f03ebe..b60f9023 100644 --- a/lib/templates/serialize_for_api.rb +++ b/lib/templates/serialize_for_api.rb @@ -6,7 +6,7 @@ module Templates only: %w[ id archived_at fields name preferences schema slug source submitters created_at updated_at - author_id external_id folder_id + author_id external_id folder_id shared_link ], methods: %i[application_key folder_name], include: { author: { only: %i[id email first_name last_name] } } diff --git a/spec/fixtures/fieldtags.docx b/spec/fixtures/fieldtags.docx new file mode 100644 index 0000000000000000000000000000000000000000..50655a04f117a99d8543ad3c81f641e3a3fa7d7a GIT binary patch literal 8860 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 literal 0 HcmV?d00001 diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb index fa2a362a..7763d8cd 100644 --- a/spec/requests/templates_spec.rb +++ b/spec/requests/templates_spec.rb @@ -83,13 +83,15 @@ describe 'Templates API' do end describe 'PUT /api/templates' do - it 'update a template' do - template = create(:template, account:, - author:, - folder:, - external_id: SecureRandom.base58(10), - preferences: template_preferences) + let(:template) do + create(:template, account:, + author:, + folder:, + external_id: SecureRandom.base58(10), + preferences: template_preferences) + end + it 'updates a template' do put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: { name: 'Updated Template Name', external_id: '123456' @@ -106,6 +108,24 @@ describe 'Templates API' do updated_at: template.updated_at }.to_json)) end + + it "enables the template's shared link" do + expect do + put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: { + shared_link: true + }.to_json + end.to change { template.reload.shared_link }.from(false).to(true) + end + + it "disables the template's shared link" do + template.update(shared_link: true) + + expect do + put "/api/templates/#{template.id}", headers: { 'x-auth-token': author.access_token.token }, params: { + shared_link: false + }.to_json + end.to change { template.reload.shared_link }.from(true).to(false) + end end describe 'DELETE /api/templates/:id' do @@ -206,6 +226,7 @@ describe 'Templates API' do name: 'sample-document' } ], + shared_link: template.shared_link, author_id: author.id, archived_at: nil, created_at: template.created_at, diff --git a/spec/system/signing_form_spec.rb b/spec/system/signing_form_spec.rb index 8bfa3e5e..83c1b30b 100644 --- a/spec/system/signing_form_spec.rb +++ b/spec/system/signing_form_spec.rb @@ -5,7 +5,9 @@ RSpec.describe 'Signing Form' do let(:author) { create(:user, account:) } context 'when the template form link is opened' do - let(:template) { create(:template, account:, author:, except_field_types: %w[phone payment stamp]) } + let(:template) do + create(:template, shared_link: true, account:, author:, except_field_types: %w[phone payment stamp]) + end before do visit start_form_path(slug: template.slug) @@ -811,7 +813,9 @@ RSpec.describe 'Signing Form' do end context 'when the template requires multiple submitters' do - let(:template) { create(:template, submitter_count: 2, account:, author:, only_field_types: %w[text]) } + let(:template) do + create(:template, shared_link: true, submitter_count: 2, account:, author:, only_field_types: %w[text]) + end context 'when default signer details are not defined' do it 'shows an explanation error message if a logged-in user associated with the template account opens the link' do diff --git a/spec/system/template_share_link_spec.rb b/spec/system/template_share_link_spec.rb new file mode 100644 index 00000000..8930812b --- /dev/null +++ b/spec/system/template_share_link_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +RSpec.describe 'Template Share Link' do + let!(:account) { create(:account) } + let!(:author) { create(:user, account:) } + let!(:template) { create(:template, account:, author:) } + + before do + sign_in(author) + end + + context 'when the template is not shareable' do + before do + visit template_path(template) + end + + it 'makes the template shareable' do + click_on 'Link' + + expect do + within '#modal' do + check 'template_shared_link' + end + end.to change { template.reload.shared_link }.from(false).to(true) + end + + it 'makes the template shareable when copying the shareable link' do + click_on 'Link' + + expect do + within '#modal' do + find('clipboard-copy').click + end + end.to change { template.reload.shared_link }.from(false).to(true) + end + + it 'copies the shareable link without changing its status' do + template.update(shared_link: true) + + click_on 'Link' + + expect do + within '#modal' do + find('clipboard-copy').click + end + end.not_to(change { template.reload.shared_link }) + end + end + + context 'when the template is already shareable' do + before do + template.update(shared_link: true) + visit template_path(template) + end + + it 'makes the template unshareable' do + click_on 'Link' + + expect do + within '#modal' do + uncheck 'template_shared_link' + end + end.to change { template.reload.shared_link }.from(true).to(false) + end + end +end