From 121a722be7a7be640c9eba967a140cb47a6ccce5 Mon Sep 17 00:00:00 2001 From: ChapsJust <2238424@carrefour.cegepvicto.ca> Date: Fri, 27 Feb 2026 12:19:36 -0500 Subject: [PATCH] Refonte --- .gitattributes | 1 + Dockerfile | 2 +- app/controllers/application_controller.rb | 13 +++-- .../submission_form/verification_step.vue | 2 +- app/javascript/template_builder/logo.vue | 15 ++--- app/models/user.rb | 4 +- .../_default_signature_row.html.erb | 1 + app/views/layouts/application.html.erb | 2 +- .../_reminder_placeholder.html.erb | 1 + .../_logo_placeholder.html.erb | 1 + app/views/shared/_settings_nav.html.erb | 2 +- app/views/sms_settings/_placeholder.html.erb | 1 + app/views/sso_settings/_placeholder.html.erb | 1 + .../_bulk_send_placeholder.html.erb | 1 + .../submissions/_send_sms_button.html.erb | 1 + .../_placeholder.html.erb | 1 + app/views/users/_role_select.html.erb | 18 +++--- config/locales/whitelabel.yml | 55 ++++++++++++++++++- config/whitelabel.yml | 11 ++++ lib/ability.rb | 48 ++++++++++++++++ lib/accounts.rb | 2 +- lib/whitelabel.rb | 12 ++++ 22 files changed, 162 insertions(+), 33 deletions(-) diff --git a/.gitattributes b/.gitattributes index 28cee3ff..c1f3ae47 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.html linguist-detectable=false +bin/* text eol=lf diff --git a/Dockerfile b/Dockerfile index b1341fcf..3b5e35d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ COPY ./tailwind.application.config.js ./tailwind.application.config.js COPY ./app/javascript ./app/javascript COPY ./app/views ./app/views -RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker +RUN echo "gem 'shakapacker'" > Gemfile && sed -i 's/\r$//' bin/shakapacker && ./bin/shakapacker FROM ruby:4.0.1-alpine AS app diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 323e8f23..5fa236c1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base rescue_from CanCan::AccessDenied do |e| Rollbar.warning(e) if defined?(Rollbar) - redirect_to root_path, alert: e.message + redirect_to root_path, alert: I18n.t('unauthorized.default', locale: current_account&.locale) end end @@ -67,12 +67,13 @@ class ApplicationController < ActionController::Base private def with_locale(&) - return yield unless current_account + locale = if current_account + (params[:lang].presence if Rails.env.development?) || current_account.locale + else + request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s.split('-').first.presence + end - locale = params[:lang].presence if Rails.env.development? - locale ||= current_account.locale - - I18n.with_locale(locale, &) + I18n.with_locale(locale || I18n.default_locale, &) end def with_browser_locale(&) diff --git a/app/javascript/submission_form/verification_step.vue b/app/javascript/submission_form/verification_step.vue index 29b3b590..cd17e179 100644 --- a/app/javascript/submission_form/verification_step.vue +++ b/app/javascript/submission_form/verification_step.vue @@ -119,7 +119,7 @@ export default { docId: this.eidEasyData.doc_id, language: this.locale, countryCode: this.countryCode, - sandbox: ['demo.docuseal.tech'].includes(location.host), + sandbox: false, enabledMethods: { signature: this.eidEasyData.available_methods }, diff --git a/app/javascript/template_builder/logo.vue b/app/javascript/template_builder/logo.vue index 8f878e14..d0c3534b 100644 --- a/app/javascript/template_builder/logo.vue +++ b/app/javascript/template_builder/logo.vue @@ -1,20 +1,13 @@ diff --git a/app/models/user.rb b/app/models/user.rb index 7eabb059..2950aeec 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -48,7 +48,9 @@ # class User < ApplicationRecord ROLES = [ - ADMIN_ROLE = 'admin' + ADMIN_ROLE = 'admin', + GESTIONNAIRE_ROLE = 'gestionnaire', + USER_ROLE = 'user' ].freeze EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/ diff --git a/app/views/esign_settings/_default_signature_row.html.erb b/app/views/esign_settings/_default_signature_row.html.erb index 899f99b3..f42a5733 100644 --- a/app/views/esign_settings/_default_signature_row.html.erb +++ b/app/views/esign_settings/_default_signature_row.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %> <%= svg_icon('discount_check_filled', class: 'w-6 h-6 text-green-500') %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9f279c64..4a500fd6 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -21,7 +21,7 @@ <% end %> <%= stylesheet_pack_tag 'application', media: 'all' %> - + <% if params[:modal].present? %> <% url_params = Rails.application.routes.recognize_path(params[:modal], method: :get) %> <% if url_params[:action] == 'new' %> diff --git a/app/views/notifications_settings/_reminder_placeholder.html.erb b/app/views/notifications_settings/_reminder_placeholder.html.erb index 38b05ce5..936123c2 100644 --- a/app/views/notifications_settings/_reminder_placeholder.html.erb +++ b/app/views/notifications_settings/_reminder_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
diff --git a/app/views/personalization_settings/_logo_placeholder.html.erb b/app/views/personalization_settings/_logo_placeholder.html.erb index 9a8358e3..14329d04 100644 --- a/app/views/personalization_settings/_logo_placeholder.html.erb +++ b/app/views/personalization_settings/_logo_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index 97b45add..de66f062 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -46,7 +46,7 @@ <% end %> <%= render 'shared/settings_nav_extra' %> <% if Docuseal.demo? || !Docuseal.multitenant? %> - <% if can?(:read, AccessToken) %> + <% if can?(:read, AccessToken) && current_user.role != User::USER_ROLE %>
  • <%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
  • diff --git a/app/views/sms_settings/_placeholder.html.erb b/app/views/sms_settings/_placeholder.html.erb index 13f2e2ea..c3788b44 100644 --- a/app/views/sms_settings/_placeholder.html.erb +++ b/app/views/sms_settings/_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
    <%= svg_icon('info_circle', class: 'w-6 h-6') %>
    diff --git a/app/views/sso_settings/_placeholder.html.erb b/app/views/sso_settings/_placeholder.html.erb index c6497097..fc814f97 100644 --- a/app/views/sso_settings/_placeholder.html.erb +++ b/app/views/sso_settings/_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
    <%= svg_icon('info_circle', class: 'w-6 h-6') %>
    diff --git a/app/views/submissions/_bulk_send_placeholder.html.erb b/app/views/submissions/_bulk_send_placeholder.html.erb index f81f6534..f7f6c064 100644 --- a/app/views/submissions/_bulk_send_placeholder.html.erb +++ b/app/views/submissions/_bulk_send_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
    <%= svg_icon('info_circle', class: 'w-6 h-6') %>
    diff --git a/app/views/submissions/_send_sms_button.html.erb b/app/views/submissions/_send_sms_button.html.erb index b8df27cf..3034866d 100644 --- a/app/views/submissions/_send_sms_button.html.erb +++ b/app/views/submissions/_send_sms_button.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
    <%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CLOUD_URL}/sign_up?#{{ redir: "#{Docuseal::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %> diff --git a/app/views/templates_code_modal/_placeholder.html.erb b/app/views/templates_code_modal/_placeholder.html.erb index 7557aa78..4234e626 100644 --- a/app/views/templates_code_modal/_placeholder.html.erb +++ b/app/views/templates_code_modal/_placeholder.html.erb @@ -1,3 +1,4 @@ +<% return unless Whitelabel.show_pro_upsells? %>
    <%= svg_icon('info_circle', class: 'w-6 h-6') %>
    diff --git a/app/views/users/_role_select.html.erb b/app/views/users/_role_select.html.erb index d14b2778..7aa6ce0a 100644 --- a/app/views/users/_role_select.html.erb +++ b/app/views/users/_role_select.html.erb @@ -1,9 +1,9 @@
    <%= f.label :role, class: 'label' %> <%= f.select :role, nil, {}, class: 'base-select' do %> - - - + + + <% end %> <% if Docuseal.multitenant? %> <% end %> - "> - <%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %> - <%= t('unlock_more_user_roles_with_docuseal_pro') %> - <%= t('learn_more') %> - + <% if Whitelabel.show_pro_upsells? %> + "> + <%= svg_icon('info_circle', class: 'w-4 h-4 inline align-text-bottom') %> + <%= t('unlock_more_user_roles_with_docuseal_pro') %> + <%= t('learn_more') %> + + <% end %>
    diff --git a/config/locales/whitelabel.yml b/config/locales/whitelabel.yml index cd78397f..e569e4f2 100644 --- a/config/locales/whitelabel.yml +++ b/config/locales/whitelabel.yml @@ -10,7 +10,7 @@ # Languages: English (en) + French (fr) # ============================================================================= -en: +en: &en_wl docuseal_trusted_signature: "Intébec Trusted Signature" sent_with_docuseal_pro_html: 'Sent with Intébec Pro' show_send_with_docuseal_pro_attribution_in_emails_html: 'Show "Sent with Intébec Pro" attribution in emails' @@ -28,13 +28,21 @@ en: your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: "Your Pro Plan has been suspended due to unpaid invoices. You can update your payment details to settle the invoice and continue using Intébec or cancel your subscription." this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'This submission has multiple signers, which prevents the use of a sharing link as it''s unclear which signer is responsible for specific fields. To resolve this, follow this guide to define the default signer details.' welcome_to_docuseal: "Welcome to Intébec" + start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: "Start a quick tour to learn how to create and send your first document with Intébec." your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: "Your email could not be reached. This may happen if there was a typo in your address or if your mailbox is not available. Please contact support@intebec.ca to log in." add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: "Add a unique Signature ID and timestamp to each signature for audit and traceability purposes along with the timestamp. Part of Intébec's 21 CFR Part 11 compliance settings." require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: "Require signers to provide a reason for signing before completing their signature (e.g., approvals, certifications). Part of Intébec's 21 CFR Part 11 compliance settings." + role_admin: "Administrator" + role_gestionnaire: "Manager" + role_user: "User" + unauthorized: + default: "You are not authorized to access this page." + manage: + all: "You are not authorized to perform this action." onboarding: support_description: "You can use our self-service AI assistant or email us at support@intebec.ca if you have any questions." -fr: +fr: &fr_wl docuseal_trusted_signature: "Signature de confiance Intébec" sent_with_docuseal_pro_html: 'Envoyé avec Intébec Pro' show_send_with_docuseal_pro_attribution_in_emails_html: 'Afficher l''attribution « Envoyé avec Intébec Pro » dans les courriels' @@ -52,9 +60,13 @@ fr: your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: "Votre plan Pro a été suspendu en raison de factures impayées. Vous pouvez mettre à jour vos informations de paiement pour régler la facture et continuer à utiliser Intébec ou annuler votre abonnement." this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html: 'Cette soumission comporte plusieurs signataires, ce qui empêche l''utilisation d''un lien de partage car on ne sait pas quel signataire est responsable de quels champs. Pour résoudre ce problème, suivez ce guide pour définir les détails du signataire par défaut.' welcome_to_docuseal: "Bienvenue sur Intébec" + start_a_quick_tour_to_learn_how_to_create_an_send_your_first_document: "Lancez une visite rapide pour apprendre à créer et envoyer votre premier document avec Intébec." your_email_could_not_be_reached_this_may_happen_if_there_was_a_typo_in_your_address_or_if_your_mailbox_is_not_available_please_contact_support_email_to_log_in: "Votre courriel n'a pas pu être atteint. Cela peut arriver s'il y a une faute de frappe dans votre adresse ou si votre boîte de réception n'est pas disponible. Veuillez contacter support@intebec.ca pour vous connecter." add_a_unique_signature_id_and_timestamp_to_each_signature_for_audit_and_traceability_purposes_along_with_the_timestamp_part_of_docuseals_21_cfr_part_11_compliance_settings: "Ajoutez un identifiant de signature unique et un horodatage à chaque signature à des fins d'audit et de traçabilité, ainsi que l'horodatage. Fait partie des paramètres de conformité 21 CFR Part 11 d'Intébec." require_signer_to_provide_a_reason_for_signing_before_completing_their_signature_e_g_approvals_certifications_part_of_docuseals_21_cfr_part_11_compliance_settings: "Exiger que le signataire fournisse une raison de signature avant de terminer sa signature (ex. : approbations, certifications). Fait partie des paramètres de conformité 21 CFR Part 11 d'Intébec." + role_admin: "Administrateur" + role_gestionnaire: "Gestionnaire" + role_user: "Utilisateur" onboarding: support_description: "Vous pouvez utiliser notre assistant IA en libre-service ou nous écrire à support@intebec.ca si vous avez des questions." # ------------------------------------------------------------------------- @@ -75,3 +87,42 @@ fr: download: "Télécharger" powered_by: "Propulsé par" open_source_documents_software: "logiciel de signature de documents" + unauthorized: + default: "Vous n'êtes pas autorisé à accéder à cette page." + manage: + all: "Vous n'êtes pas autorisé à effectuer cette action." + devise: + sessions: + signed_in: "Connexion réussie." + signed_out: "Déconnexion réussie." + already_signed_in: "Vous êtes déjà connecté." + failure: + already_authenticated: "Vous êtes déjà connecté." + unauthenticated: "Vous devez vous connecter pour accéder à cette page." + locked: "Votre compte est verrouillé." + invalid: "Adresse courriel ou mot de passe invalide." + last_attempt: "Il vous reste une tentative avant que votre compte soit verrouillé." + not_found_in_database: "Adresse courriel ou mot de passe invalide." + timeout: "Votre session a expiré. Veuillez vous reconnecter." + inactive: "Votre compte n'est pas encore activé." + passwords: + send_instructions: "Vous recevrez un courriel avec les instructions pour réinitialiser votre mot de passe." + send_paranoid_instructions: "Si votre courriel est dans notre base de données, vous recevrez un lien pour réinitialiser votre mot de passe." + updated: "Votre mot de passe a été modifié. Vous êtes maintenant connecté." + updated_not_active: "Votre mot de passe a été modifié avec succès." + mailer: + reset_password_instructions: + subject: "Réinitialisation de votre mot de passe" + +# --------------------------------------------------------------------------- +# Regional locale aliases — inherit all overrides from the base locales above +# (mirrors the YAML anchor pattern used in i18n.yml) +# --------------------------------------------------------------------------- +en-US: + <<: *en_wl + +en-GB: + <<: *en_wl + +fr-FR: + <<: *fr_wl diff --git a/config/whitelabel.yml b/config/whitelabel.yml index f5209fc0..b3aa8be5 100644 --- a/config/whitelabel.yml +++ b/config/whitelabel.yml @@ -175,3 +175,14 @@ features: # Show the Discord link show_discord_link: false + + # Show "Upgrade to Pro" upsell banners/buttons throughout the app + # Set to false to hide all Pro upgrade prompts (recommended for self-hosted) + show_pro_upsells: false + +# --------------------------------------------------------------------------- +# Internal / technical settings +# --------------------------------------------------------------------------- +internal: + # Domain used for auto-generated duplicate/test account emails (never real addresses) + temp_email_domain: "intebec.ca" diff --git a/lib/ability.rb b/lib/ability.rb index a721e089..d83f2616 100644 --- a/lib/ability.rb +++ b/lib/ability.rb @@ -4,6 +4,20 @@ class Ability include CanCan::Ability def initialize(user) + case user.role + when User::ADMIN_ROLE + admin_abilities(user) + when User::GESTIONNAIRE_ROLE + gestionnaire_abilities(user) + when User::USER_ROLE + user_abilities(user) + end + end + + private + + # Accès complet à tout le compte + def admin_abilities(user) can %i[read create update], Template, Abilities::TemplateConditions.collection(user) do |template| Abilities::TemplateConditions.entity(template, user:, ability: 'manage') end @@ -22,4 +36,38 @@ class Ability can :manage, AccessToken, user_id: user.id can :manage, WebhookUrl, account_id: user.account_id end + + # Peut créer/gérer documents et envois — pas les paramètres ni les utilisateurs + def gestionnaire_abilities(user) + can %i[read create update], Template, Abilities::TemplateConditions.collection(user) do |template| + Abilities::TemplateConditions.entity(template, user:, ability: 'manage') + end + + can :destroy, Template, account_id: user.account_id + can :manage, TemplateFolder, account_id: user.account_id + can :manage, TemplateSharing, template: { account_id: user.account_id } + can :manage, Submission, account_id: user.account_id + can :manage, Submitter, account_id: user.account_id + can :manage, EncryptedUserConfig, user_id: user.id + can :manage, UserConfig, user_id: user.id + can :manage, User, id: user.id + can :read, Account, id: user.account_id + can :manage, AccessToken, user_id: user.id + end + + # Lecture seule — ne peut pas créer ni modifier + def user_abilities(user) + can :read, Template, Abilities::TemplateConditions.collection(user) do |template| + Abilities::TemplateConditions.entity(template, user:) + end + + can :read, TemplateFolder, account_id: user.account_id + can :read, Submission, account_id: user.account_id + can :read, Submitter, account_id: user.account_id + can :manage, EncryptedUserConfig, user_id: user.id + can :manage, UserConfig, user_id: user.id + can :manage, User, id: user.id + can :read, Account, id: user.account_id + can :manage, AccessToken, user_id: user.id + end end diff --git a/lib/accounts.rb b/lib/accounts.rb index fbbf688c..53971d24 100644 --- a/lib/accounts.rb +++ b/lib/accounts.rb @@ -13,7 +13,7 @@ module Accounts new_user.uuid = SecureRandom.uuid new_user.account = new_account new_user.encrypted_password = SecureRandom.hex - new_user.email = "#{SecureRandom.hex}@docuseal.com" + new_user.email = "#{SecureRandom.hex}@#{Whitelabel.temp_email_domain}" account.templates.each do |template| new_template = template.dup diff --git a/lib/whitelabel.rb b/lib/whitelabel.rb index f4e360e0..30c45f68 100644 --- a/lib/whitelabel.rb +++ b/lib/whitelabel.rb @@ -233,6 +233,18 @@ module Whitelabel config.dig('features', 'show_discord_link') == true end + def show_pro_upsells? + config.dig('features', 'show_pro_upsells') == true + end + + # ----------------------------------------------------------------------- + # Internal / technical + # ----------------------------------------------------------------------- + + def temp_email_domain + config.dig('internal', 'temp_email_domain') || 'example.com' + end + private def load_config