Merge pull request #10644 from colinux/fix-helpscout-invalid-email
ETQ utilisateur, le form de contact détecte les typos d'email et valide les champs avant de l'envoyer à HS
This commit is contained in:
commit
3b82621229
28 changed files with 800 additions and 768 deletions
|
@ -127,6 +127,8 @@ module Dsfr
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hint? = hint.present?
|
||||||
|
|
||||||
def password?
|
def password?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -142,15 +144,6 @@ module Dsfr
|
||||||
def hintable?
|
def hintable?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def hint?
|
|
||||||
return true if get_slot(:hint).present?
|
|
||||||
|
|
||||||
maybe_hint = I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
|
|
||||||
maybe_hint_html = I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}_html")
|
|
||||||
|
|
||||||
maybe_hint || maybe_hint_html
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
76
app/controllers/contact_controller.rb
Normal file
76
app/controllers/contact_controller.rb
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
class ContactController < ApplicationController
|
||||||
|
invisible_captcha only: [:create], on_spam: :redirect_to_root
|
||||||
|
|
||||||
|
def index
|
||||||
|
@form = ContactForm.new(tags: contact_form_params.fetch(:tags, []), dossier_id: dossier&.id)
|
||||||
|
@form.user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin
|
||||||
|
@form = ContactForm.new(tags: contact_form_params.fetch(:tags, []), for_admin: true)
|
||||||
|
@form.user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
if direct_message?
|
||||||
|
create_commentaire!
|
||||||
|
flash.notice = t('.direct_message_sent')
|
||||||
|
|
||||||
|
redirect_to messagerie_dossier_path(dossier)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
@form = ContactForm.new(contact_form_params)
|
||||||
|
@form.user = current_user
|
||||||
|
|
||||||
|
if @form.save
|
||||||
|
@form.create_conversation_later
|
||||||
|
flash.notice = t('.message_sent')
|
||||||
|
|
||||||
|
redirect_to root_path
|
||||||
|
else
|
||||||
|
flash.alert = @form.errors.full_messages
|
||||||
|
render @form.for_admin ? :admin : :index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_commentaire!
|
||||||
|
attributes = {
|
||||||
|
piece_jointe: contact_form_params[:piece_jointe],
|
||||||
|
body: "[#{contact_form_params[:subject]}]<br><br>#{contact_form_params[:text]}"
|
||||||
|
}
|
||||||
|
CommentaireService.create!(current_user, dossier, attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def browser_name
|
||||||
|
if browser.known?
|
||||||
|
"#{browser.name} #{browser.version} (#{browser.platform.name})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_message?
|
||||||
|
return false unless user_signed_in?
|
||||||
|
return false unless contact_form_params[:question_type] == ContactForm::TYPE_INSTRUCTION
|
||||||
|
|
||||||
|
dossier&.messagerie_available?
|
||||||
|
end
|
||||||
|
|
||||||
|
def dossier
|
||||||
|
@dossier ||= current_user&.dossiers&.find_by(id: contact_form_params[:dossier_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_to_root
|
||||||
|
redirect_to root_path, alert: t('invisible_captcha.sentence_for_humans')
|
||||||
|
end
|
||||||
|
|
||||||
|
def contact_form_params
|
||||||
|
keys = [:email, :subject, :text, :question_type, :dossier_id, :piece_jointe, :phone, :for_admin, tags: []]
|
||||||
|
if params.key?(:contact_form) # submitting form
|
||||||
|
params.require(:contact_form).permit(*keys)
|
||||||
|
else
|
||||||
|
params.permit(:dossier_id, tags: []) # prefilling form
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,101 +0,0 @@
|
||||||
class SupportController < ApplicationController
|
|
||||||
invisible_captcha only: [:create], on_spam: :redirect_to_root
|
|
||||||
|
|
||||||
def index
|
|
||||||
setup_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def admin
|
|
||||||
setup_context_admin
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
if direct_message? && create_commentaire
|
|
||||||
flash.notice = "Votre message a été envoyé sur la messagerie de votre dossier."
|
|
||||||
|
|
||||||
redirect_to messagerie_dossier_path(dossier)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
create_conversation_later
|
|
||||||
flash.notice = "Votre message a été envoyé."
|
|
||||||
|
|
||||||
if params[:admin]
|
|
||||||
redirect_to root_path(formulaire_contact_admin_submitted: true)
|
|
||||||
else
|
|
||||||
redirect_to root_path(formulaire_contact_general_submitted: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def setup_context
|
|
||||||
@dossier_id = dossier&.id
|
|
||||||
@tags = tags
|
|
||||||
@options = Helpscout::FormAdapter.options
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_context_admin
|
|
||||||
@tags = tags
|
|
||||||
@options = Helpscout::FormAdapter.admin_options
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_conversation_later
|
|
||||||
if params[:piece_jointe]
|
|
||||||
blob = ActiveStorage::Blob.create_and_upload!(
|
|
||||||
io: params[:piece_jointe].tempfile,
|
|
||||||
filename: params[:piece_jointe].original_filename,
|
|
||||||
content_type: params[:piece_jointe].content_type,
|
|
||||||
identify: false
|
|
||||||
).tap(&:scan_for_virus_later)
|
|
||||||
end
|
|
||||||
|
|
||||||
HelpscoutCreateConversationJob.perform_later(
|
|
||||||
blob_id: blob&.id,
|
|
||||||
subject: params[:subject],
|
|
||||||
email: email,
|
|
||||||
phone: params[:phone],
|
|
||||||
text: params[:text],
|
|
||||||
dossier_id: dossier&.id,
|
|
||||||
browser: browser_name,
|
|
||||||
tags: tags
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_commentaire
|
|
||||||
attributes = {
|
|
||||||
piece_jointe: params[:piece_jointe],
|
|
||||||
body: "[#{params[:subject]}]<br><br>#{params[:text]}"
|
|
||||||
}
|
|
||||||
CommentaireService.create!(current_user, dossier, attributes)
|
|
||||||
end
|
|
||||||
|
|
||||||
def tags
|
|
||||||
[params[:tags], params[:type]].flatten.compact
|
|
||||||
.map { |tag| tag.split(',') }
|
|
||||||
.flatten
|
|
||||||
.compact_blank.uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def browser_name
|
|
||||||
if browser.known?
|
|
||||||
"#{browser.name} #{browser.version} (#{browser.platform.name})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def direct_message?
|
|
||||||
user_signed_in? && params[:type] == Helpscout::FormAdapter::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available?
|
|
||||||
end
|
|
||||||
|
|
||||||
def dossier
|
|
||||||
@dossier ||= current_user&.dossiers&.find_by(id: params[:dossier_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def email
|
|
||||||
current_user&.email || params[:email]
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_to_root
|
|
||||||
redirect_to root_path, alert: t('invisible_captcha.sentence_for_humans')
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -74,7 +74,7 @@ module ApplicationHelper
|
||||||
tags, type, dossier_id = options.values_at(:tags, :type, :dossier_id)
|
tags, type, dossier_id = options.values_at(:tags, :type, :dossier_id)
|
||||||
options.except!(:tags, :type, :dossier_id)
|
options.except!(:tags, :type, :dossier_id)
|
||||||
|
|
||||||
params = { tags: tags, type: type, dossier_id: dossier_id }.compact
|
params = { tags: Array.wrap(tags), type: type, dossier_id: dossier_id }.compact
|
||||||
link_to title, contact_url(params), options
|
link_to title, contact_url(params), options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ApplicationController } from './application_controller';
|
import { ApplicationController } from './application_controller';
|
||||||
import { hide, show } from '@utils';
|
import { hide, show } from '@utils';
|
||||||
|
|
||||||
export class SupportController extends ApplicationController {
|
export class ContactController extends ApplicationController {
|
||||||
static targets = ['inputRadio', 'content'];
|
static targets = ['inputRadio', 'content'];
|
||||||
|
|
||||||
declare readonly inputRadioTargets: HTMLInputElement[];
|
declare readonly inputRadioTargets: HTMLInputElement[];
|
|
@ -8,14 +8,49 @@ class HelpscoutCreateConversationJob < ApplicationJob
|
||||||
|
|
||||||
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
||||||
|
|
||||||
def perform(blob_id: nil, **args)
|
attr_reader :contact_form
|
||||||
if blob_id.present?
|
attr_reader :api
|
||||||
blob = ActiveStorage::Blob.find(blob_id)
|
|
||||||
raise FileNotScannedYetError if blob.virus_scanner.pending?
|
|
||||||
|
|
||||||
blob = nil unless blob.virus_scanner.safe?
|
def perform(contact_form)
|
||||||
|
@contact_form = contact_form
|
||||||
|
|
||||||
|
if contact_form.piece_jointe.attached?
|
||||||
|
raise FileNotScannedYetError if contact_form.piece_jointe.virus_scanner.pending?
|
||||||
end
|
end
|
||||||
|
|
||||||
Helpscout::FormAdapter.new(**args, blob:).send_form
|
@api = Helpscout::API.new
|
||||||
|
|
||||||
|
create_conversation
|
||||||
|
|
||||||
|
contact_form.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_conversation
|
||||||
|
response = api.create_conversation(
|
||||||
|
contact_form.email,
|
||||||
|
contact_form.subject,
|
||||||
|
contact_form.text,
|
||||||
|
safe_blob
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.success?
|
||||||
|
conversation_id = response.headers['Resource-ID']
|
||||||
|
|
||||||
|
if contact_form.phone.present?
|
||||||
|
api.add_phone_number(contact_form.email, contact_form.phone)
|
||||||
|
end
|
||||||
|
|
||||||
|
api.add_tags(conversation_id, contact_form.tags)
|
||||||
|
else
|
||||||
|
fail "Error while creating conversation: #{response.response_code} '#{response.body}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def safe_blob
|
||||||
|
return if !contact_form.piece_jointe.virus_scanner&.safe?
|
||||||
|
|
||||||
|
contact_form.piece_jointe
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
class Helpscout::FormAdapter
|
|
||||||
attr_reader :params
|
|
||||||
|
|
||||||
def self.options
|
|
||||||
[
|
|
||||||
[I18n.t(:question, scope: [:support, :index, TYPE_INFO]), TYPE_INFO, I18n.t("links.common.faq.contacter_service_en_charge_url")],
|
|
||||||
[I18n.t(:question, scope: [:support, :index, TYPE_PERDU]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL],
|
|
||||||
[I18n.t(:question, scope: [:support, :index, TYPE_INSTRUCTION]), TYPE_INSTRUCTION, I18n.t("links.common.faq.ou_en_est_mon_dossier_url")],
|
|
||||||
[I18n.t(:question, scope: [:support, :index, TYPE_AMELIORATION]), TYPE_AMELIORATION, FEATURE_UPVOTE_URL],
|
|
||||||
[I18n.t(:question, scope: [:support, :index, TYPE_AUTRE]), TYPE_AUTRE]
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.admin_options
|
|
||||||
[
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_QUESTION], app_name: Current.application_name), ADMIN_TYPE_QUESTION],
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_RDV], app_name: Current.application_name), ADMIN_TYPE_RDV],
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_SOUCIS], app_name: Current.application_name), ADMIN_TYPE_SOUCIS],
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_PRODUIT]), ADMIN_TYPE_PRODUIT],
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_DEMANDE_COMPTE]), ADMIN_TYPE_DEMANDE_COMPTE],
|
|
||||||
[I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_AUTRE]), ADMIN_TYPE_AUTRE]
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(params = {}, api = nil)
|
|
||||||
@params = params
|
|
||||||
@api = api || Helpscout::API.new
|
|
||||||
end
|
|
||||||
|
|
||||||
TYPE_INFO = 'procedure_info'
|
|
||||||
TYPE_PERDU = 'lost_user'
|
|
||||||
TYPE_INSTRUCTION = 'instruction_info'
|
|
||||||
TYPE_AMELIORATION = 'product'
|
|
||||||
TYPE_AUTRE = 'other'
|
|
||||||
|
|
||||||
ADMIN_TYPE_RDV = 'admin demande rdv'
|
|
||||||
ADMIN_TYPE_QUESTION = 'admin question'
|
|
||||||
ADMIN_TYPE_SOUCIS = 'admin soucis'
|
|
||||||
ADMIN_TYPE_PRODUIT = 'admin suggestion produit'
|
|
||||||
ADMIN_TYPE_DEMANDE_COMPTE = 'admin demande compte'
|
|
||||||
ADMIN_TYPE_AUTRE = 'admin autre'
|
|
||||||
|
|
||||||
def send_form
|
|
||||||
conversation_id = create_conversation
|
|
||||||
|
|
||||||
if conversation_id.present?
|
|
||||||
add_tags(conversation_id)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def add_tags(conversation_id)
|
|
||||||
@api.add_tags(conversation_id, tags)
|
|
||||||
end
|
|
||||||
|
|
||||||
def tags
|
|
||||||
(params[:tags].presence || []) + ['contact form']
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_conversation
|
|
||||||
response = @api.create_conversation(
|
|
||||||
params[:email],
|
|
||||||
params[:subject],
|
|
||||||
params[:text],
|
|
||||||
params[:blob]
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.success?
|
|
||||||
if params[:phone].present?
|
|
||||||
@api.add_phone_number(params[:email], params[:phone])
|
|
||||||
end
|
|
||||||
response.headers['Resource-ID']
|
|
||||||
else
|
|
||||||
raise StandardError, "Error while creating conversation: #{response.response_code} '#{response.body}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
81
app/models/contact_form.rb
Normal file
81
app/models/contact_form.rb
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
class ContactForm < ApplicationRecord
|
||||||
|
attr_reader :options
|
||||||
|
|
||||||
|
belongs_to :user, optional: true, dependent: :destroy
|
||||||
|
|
||||||
|
after_initialize :set_options
|
||||||
|
before_validation :normalize_strings
|
||||||
|
before_validation :sanitize_email
|
||||||
|
before_save :add_default_tags
|
||||||
|
|
||||||
|
validates :email, presence: true, strict_email: true, if: :require_email?
|
||||||
|
validates :subject, presence: true
|
||||||
|
validates :text, presence: true
|
||||||
|
validates :question_type, presence: true
|
||||||
|
|
||||||
|
has_one_attached :piece_jointe
|
||||||
|
|
||||||
|
TYPE_INFO = 'procedure_info'
|
||||||
|
TYPE_PERDU = 'lost_user'
|
||||||
|
TYPE_INSTRUCTION = 'instruction_info'
|
||||||
|
TYPE_AMELIORATION = 'product'
|
||||||
|
TYPE_AUTRE = 'other'
|
||||||
|
|
||||||
|
ADMIN_TYPE_RDV = 'admin_demande_rdv'
|
||||||
|
ADMIN_TYPE_QUESTION = 'admin_question'
|
||||||
|
ADMIN_TYPE_SOUCIS = 'admin_soucis'
|
||||||
|
ADMIN_TYPE_PRODUIT = 'admin_suggestion_produit'
|
||||||
|
ADMIN_TYPE_DEMANDE_COMPTE = 'admin_demande_compte'
|
||||||
|
ADMIN_TYPE_AUTRE = 'admin_autre'
|
||||||
|
|
||||||
|
def self.default_options
|
||||||
|
[
|
||||||
|
[I18n.t(:question, scope: [:contact, :index, TYPE_INFO]), TYPE_INFO, I18n.t("links.common.faq.contacter_service_en_charge_url")],
|
||||||
|
[I18n.t(:question, scope: [:contact, :index, TYPE_PERDU]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL],
|
||||||
|
[I18n.t(:question, scope: [:contact, :index, TYPE_INSTRUCTION]), TYPE_INSTRUCTION, I18n.t("links.common.faq.ou_en_est_mon_dossier_url")],
|
||||||
|
[I18n.t(:question, scope: [:contact, :index, TYPE_AMELIORATION]), TYPE_AMELIORATION, FEATURE_UPVOTE_URL],
|
||||||
|
[I18n.t(:question, scope: [:contact, :index, TYPE_AUTRE]), TYPE_AUTRE]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.admin_options
|
||||||
|
[
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_QUESTION], app_name: Current.application_name), ADMIN_TYPE_QUESTION],
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_RDV], app_name: Current.application_name), ADMIN_TYPE_RDV],
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_SOUCIS], app_name: Current.application_name), ADMIN_TYPE_SOUCIS],
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_PRODUIT]), ADMIN_TYPE_PRODUIT],
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_DEMANDE_COMPTE]), ADMIN_TYPE_DEMANDE_COMPTE],
|
||||||
|
[I18n.t(:question, scope: [:contact, :admin, ADMIN_TYPE_AUTRE]), ADMIN_TYPE_AUTRE]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_admin=(value)
|
||||||
|
super(value)
|
||||||
|
set_options
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_conversation_later
|
||||||
|
HelpscoutCreateConversationJob.perform_later(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_email? = user.blank?
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def normalize_strings
|
||||||
|
self.subject = subject&.strip
|
||||||
|
self.text = text&.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize_email
|
||||||
|
self.email = EmailSanitizableConcern::EmailSanitizer.sanitize(email) if email.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_default_tags
|
||||||
|
self.tags = tags.push('contact form', question_type).uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_options
|
||||||
|
@options = for_admin? ? self.class.admin_options : self.class.default_options
|
||||||
|
end
|
||||||
|
end
|
60
app/views/contact/_form.html.haml
Normal file
60
app/views/contact/_form.html.haml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
= form_for form, url: contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :contact } do |f|
|
||||||
|
%p.fr-hint-text= t('asterisk_html', scope: [:utils])
|
||||||
|
|
||||||
|
- if form.require_email?
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { autocomplete: 'email' }) do |c|
|
||||||
|
- c.with_label { ContactForm.human_attribute_name(form.for_admin? ? :email_pro : :email) }
|
||||||
|
|
||||||
|
%fieldset.fr-fieldset{ name: "question_type" }
|
||||||
|
%legend.fr-fieldset__legend.fr-fieldset__legend--regular
|
||||||
|
= t('.your_question')
|
||||||
|
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||||
|
.fr-fieldset__content
|
||||||
|
- form.options.each do |(question, question_type, link)|
|
||||||
|
.fr-radio-group
|
||||||
|
= f.radio_button :question_type, question_type, required: true, data: {"contact-target": "inputRadio" }, checked: question_type == form.question_type
|
||||||
|
= f.label "question_type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do
|
||||||
|
= question
|
||||||
|
|
||||||
|
- if link.present?
|
||||||
|
.fr-ml-3w{ id: "card-#{question_type}",
|
||||||
|
class: class_names('hidden' => question_type != form.question_type),
|
||||||
|
"aria-hidden": question_type != form.question_type,
|
||||||
|
data: { "contact-target": "content" } }
|
||||||
|
= render Dsfr::CalloutComponent.new(title: t('.our_answer')) do |c|
|
||||||
|
- c.with_html_body do
|
||||||
|
-# i18n-tasks-use t("contact.index.#{question_type}.answer_html")
|
||||||
|
= t('answer_html', scope: [:contact, :index, question_type], base_url: Current.application_base_url, "link_#{question_type}": link)
|
||||||
|
|
||||||
|
|
||||||
|
- if form.for_admin?
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :phone, required: false)
|
||||||
|
- else
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :dossier_id, required: false)
|
||||||
|
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :subject)
|
||||||
|
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :text, input_type: :text_area, opts: { rows: 6 })
|
||||||
|
|
||||||
|
- if !form.for_admin?
|
||||||
|
.fr-upload-group
|
||||||
|
= f.label :piece_jointe, class: 'fr-label' do
|
||||||
|
= t('pj', scope: [:utils])
|
||||||
|
%span.fr-hint-text
|
||||||
|
= t('.notice_upload_group')
|
||||||
|
|
||||||
|
%p.notice.hidden{ data: { 'contact-type-only': ContactForm::TYPE_AMELIORATION } }
|
||||||
|
= t('.notice_pj_product')
|
||||||
|
%p.notice.hidden{ data: { 'contact-type-only': ContactForm::TYPE_AUTRE } }
|
||||||
|
= t('.notice_pj_other')
|
||||||
|
= f.file_field :piece_jointe, class: 'fr-upload', accept: '.jpg, .jpeg, .png, .pdf'
|
||||||
|
|
||||||
|
- f.object.tags.each_with_index do |tag, index|
|
||||||
|
= f.hidden_field :tags, name: f.field_name(:tags, multiple: true), id: f.field_id(:tag, index), value: tag
|
||||||
|
|
||||||
|
= f.hidden_field :for_admin
|
||||||
|
|
||||||
|
= invisible_captcha
|
||||||
|
|
||||||
|
.fr-input-group.fr-my-3w
|
||||||
|
= f.submit t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn', data: { disable: true }
|
12
app/views/contact/admin.html.haml
Normal file
12
app/views/contact/admin.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
- content_for(:title, t('.contact_team'))
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
|
#contact-form
|
||||||
|
.fr-container
|
||||||
|
%h1
|
||||||
|
= t('.contact_team')
|
||||||
|
|
||||||
|
.fr-highlight= t('.admin_intro_html', contact_path: contact_path)
|
||||||
|
|
||||||
|
= render partial: "form", object: @form
|
12
app/views/contact/index.html.haml
Normal file
12
app/views/contact/index.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
- content_for(:title, t('.contact'))
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
|
#contact-form
|
||||||
|
.fr-container
|
||||||
|
%h1
|
||||||
|
= t('.contact')
|
||||||
|
|
||||||
|
.fr-highlight= t('.intro_html')
|
||||||
|
|
||||||
|
= render partial: "form", object: @form
|
|
@ -1,49 +0,0 @@
|
||||||
- content_for(:title, 'Contact')
|
|
||||||
|
|
||||||
#contact-form
|
|
||||||
.fr-container
|
|
||||||
%h1
|
|
||||||
= t('.contact_team')
|
|
||||||
|
|
||||||
.description
|
|
||||||
= t('.admin_intro_html', contact_path: contact_path)
|
|
||||||
%br
|
|
||||||
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
|
|
||||||
|
|
||||||
= form_tag contact_path, method: :post, class: 'form' do |f|
|
|
||||||
- if !user_signed_in?
|
|
||||||
.contact-champ
|
|
||||||
= label_tag :email do
|
|
||||||
= t('.pro_mail')
|
|
||||||
%span.mandatory *
|
|
||||||
= text_field_tag :email, params[:email], required: true
|
|
||||||
|
|
||||||
.contact-champ
|
|
||||||
= label_tag :type do
|
|
||||||
= t('.your_question')
|
|
||||||
%span.mandatory *
|
|
||||||
= select_tag :type, options_for_select(@options, params[:type])
|
|
||||||
|
|
||||||
.contact-champ
|
|
||||||
= label_tag :phone do
|
|
||||||
= t('.pro_phone_number')
|
|
||||||
= text_field_tag :phone
|
|
||||||
|
|
||||||
.contact-champ
|
|
||||||
= label_tag :subject do
|
|
||||||
= t('subject', scope: [:utils])
|
|
||||||
= text_field_tag :subject, params[:subject], required: false
|
|
||||||
|
|
||||||
.contact-champ
|
|
||||||
= label_tag :text do
|
|
||||||
= t('message', scope: [:utils])
|
|
||||||
%span.mandatory *
|
|
||||||
= text_area_tag :text, params[:text], rows: 6, required: true
|
|
||||||
|
|
||||||
= invisible_captcha
|
|
||||||
|
|
||||||
= hidden_field_tag :tags, @tags&.join(',')
|
|
||||||
= hidden_field_tag :admin, true
|
|
||||||
|
|
||||||
.send-wrapper
|
|
||||||
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'button send primary'
|
|
|
@ -1,77 +0,0 @@
|
||||||
- content_for(:title, t('.contact'))
|
|
||||||
- content_for :footer do
|
|
||||||
= render partial: "root/footer"
|
|
||||||
|
|
||||||
#contact-form
|
|
||||||
.fr-container
|
|
||||||
%h1
|
|
||||||
= t('.contact')
|
|
||||||
|
|
||||||
= form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
|
|
||||||
|
|
||||||
.description
|
|
||||||
.recommandations
|
|
||||||
= t('.intro_html')
|
|
||||||
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
|
|
||||||
|
|
||||||
- if !user_signed_in?
|
|
||||||
.fr-input-group
|
|
||||||
= label_tag :email, class: 'fr-label' do
|
|
||||||
Email
|
|
||||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
|
||||||
%span.fr-hint-text
|
|
||||||
= t('.notice_email')
|
|
||||||
= email_field_tag :email, params[:email], required: true, autocomplete: 'email', class: 'fr-input'
|
|
||||||
|
|
||||||
%fieldset.fr-fieldset{ name: "type" }
|
|
||||||
%legend.fr-fieldset__legend
|
|
||||||
= t('.your_question')
|
|
||||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
|
||||||
.fr-fieldset__content
|
|
||||||
- @options.each do |(question, question_type, link)|
|
|
||||||
.fr-radio-group
|
|
||||||
= radio_button_tag :type, question_type, false, required: true, data: {"support-target": "inputRadio" }
|
|
||||||
= label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do
|
|
||||||
= question
|
|
||||||
|
|
||||||
- if link.present?
|
|
||||||
.fr-ml-3w.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } }
|
|
||||||
= render Dsfr::CalloutComponent.new(title: t('.our_answer')) do |c|
|
|
||||||
- c.with_html_body do
|
|
||||||
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")
|
|
||||||
= t('answer_html', scope: [:support, :index, question_type], base_url: Current.application_base_url, "link_#{question_type}": link)
|
|
||||||
|
|
||||||
.fr-input-group
|
|
||||||
= label_tag :dossier_id, t('file_number', scope: [:utils]), class: 'fr-label'
|
|
||||||
= text_field_tag :dossier_id, @dossier_id, class: 'fr-input'
|
|
||||||
|
|
||||||
.fr-input-group
|
|
||||||
= label_tag :subject, class: 'fr-label' do
|
|
||||||
= t('subject', scope: [:utils])
|
|
||||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
|
||||||
= text_field_tag :subject, params[:subject], required: true, class: 'fr-input'
|
|
||||||
|
|
||||||
.fr-input-group
|
|
||||||
= label_tag :text, class: 'fr-label' do
|
|
||||||
= t('message', scope: [:utils])
|
|
||||||
= render EditableChamp::AsteriskMandatoryComponent.new
|
|
||||||
= text_area_tag :text, params[:text], rows: 6, required: true, class: 'fr-input'
|
|
||||||
|
|
||||||
.fr-upload-group
|
|
||||||
= label_tag :piece_jointe, class: 'fr-label' do
|
|
||||||
= t('pj', scope: [:utils])
|
|
||||||
%span.fr-hint-text
|
|
||||||
= t('.notice_upload_group')
|
|
||||||
|
|
||||||
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
|
|
||||||
= t('.notice_pj_product')
|
|
||||||
%p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } }
|
|
||||||
= t('.notice_pj_other')
|
|
||||||
= file_field_tag :piece_jointe, class: 'fr-upload', accept: '.jpg, .jpeg, .png, .pdf'
|
|
||||||
|
|
||||||
= hidden_field_tag :tags, @tags&.join(',')
|
|
||||||
|
|
||||||
= invisible_captcha
|
|
||||||
|
|
||||||
.send-wrapper.fr-my-3w
|
|
||||||
= button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn send'
|
|
|
@ -102,6 +102,7 @@ ignore_unused:
|
||||||
- 'activerecord.models.*'
|
- 'activerecord.models.*'
|
||||||
- 'activerecord.attributes.*'
|
- 'activerecord.attributes.*'
|
||||||
- 'activemodel.attributes.map_filter.*'
|
- 'activemodel.attributes.map_filter.*'
|
||||||
|
- 'activemodel.attributes.helpscout/form.*'
|
||||||
- 'activerecord.errors.*'
|
- 'activerecord.errors.*'
|
||||||
- 'errors.messages.blank'
|
- 'errors.messages.blank'
|
||||||
- 'errors.messages.content_type_invalid'
|
- 'errors.messages.content_type_invalid'
|
||||||
|
|
|
@ -52,9 +52,6 @@ en:
|
||||||
asterisk_html: "Fields marked by an asterisk ( <svg aria-label='required' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>required</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) are mandatory."
|
asterisk_html: "Fields marked by an asterisk ( <svg aria-label='required' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>required</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) are mandatory."
|
||||||
mandatory_champs: All fields are mandatory.
|
mandatory_champs: All fields are mandatory.
|
||||||
no_mandatory: (optional)
|
no_mandatory: (optional)
|
||||||
file_number: File number
|
|
||||||
subject: Subject
|
|
||||||
message: Message
|
|
||||||
send_mail: Send message
|
send_mail: Send message
|
||||||
new_tab: New tab
|
new_tab: New tab
|
||||||
helpers:
|
helpers:
|
||||||
|
|
|
@ -43,9 +43,6 @@ fr:
|
||||||
asterisk_html: "Les champs suivis d’un astérisque ( <svg aria-label='obligatoire' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>obligatoire</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) sont obligatoires."
|
asterisk_html: "Les champs suivis d’un astérisque ( <svg aria-label='obligatoire' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>obligatoire</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) sont obligatoires."
|
||||||
mandatory_champs: Tous les champs sont obligatoires.
|
mandatory_champs: Tous les champs sont obligatoires.
|
||||||
no_mandatory: (facultatif)
|
no_mandatory: (facultatif)
|
||||||
file_number: Numéro de dossier
|
|
||||||
subject: Sujet
|
|
||||||
message: Message
|
|
||||||
send_mail: Envoyer le message
|
send_mail: Envoyer le message
|
||||||
new_tab: "Nouvel onglet"
|
new_tab: "Nouvel onglet"
|
||||||
helpers:
|
helpers:
|
||||||
|
|
73
config/locales/views/contact/en.yml
Normal file
73
config/locales/views/contact/en.yml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
contact_form:
|
||||||
|
email: 'Your email address'
|
||||||
|
email_pro: Professional email address
|
||||||
|
phone: Professional phone number (direct line)
|
||||||
|
subject: Subject
|
||||||
|
text: Message
|
||||||
|
dossier_id: File number
|
||||||
|
hints:
|
||||||
|
email: 'Example: address@mail.com'
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
contact_form:
|
||||||
|
invalid_email_format: 'is not valid'
|
||||||
|
contact:
|
||||||
|
form:
|
||||||
|
your_question: Your question
|
||||||
|
our_answer: Our answer
|
||||||
|
notice_pj_product: A screenshot can help us identify the element to improve.
|
||||||
|
notice_pj_other: A screenshot can help us identify the issue.
|
||||||
|
notice_upload_group: 'Maximum size: 200 MB. Supported formats: jpg, png, pdf.'
|
||||||
|
index:
|
||||||
|
contact: Contact
|
||||||
|
intro_html:
|
||||||
|
'<p>Contact us via this form and we will answer you as quickly as possible.</p>
|
||||||
|
<p>Make sure you provide all the required information so we can help you in the best way.</p>'
|
||||||
|
procedure_info:
|
||||||
|
question: I've encountered a problem while completing my application
|
||||||
|
answer_html:
|
||||||
|
'<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
|
||||||
|
<p>If you have questions about the information requested, <a href="%{link_procedure_info}">contact the service in charge of the procedure (FAQ)</a>.</p>'
|
||||||
|
instruction_info:
|
||||||
|
question: I have a question about the instruction of my application
|
||||||
|
answer_html:
|
||||||
|
'<p>If you have questions about the instruction of your application (response delay for example), <a href="%{link_instruction_info}">contact directly the instructor via our mail system (FAQ)</a>.</p>
|
||||||
|
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>'
|
||||||
|
product:
|
||||||
|
question: I have an idea to improve the website
|
||||||
|
answer_html:
|
||||||
|
'<p>Got an idea? <a href="%{link_product}" rel="noopener noreferrer" target="_blank" title="Please check our enhancement dashboard - New tab">Please check our enhancement dashboard</a> :</p>
|
||||||
|
<ul><li>Vote for your priority improvements</li>
|
||||||
|
<li>Share your own ideas</li></ul>'
|
||||||
|
lost_user:
|
||||||
|
question: I am having trouble finding the procedure I am looking for
|
||||||
|
answer_html:
|
||||||
|
'<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
|
||||||
|
It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
|
||||||
|
<p>You can <a href="%{link_lost_user}">find here the most popular procedures (licence, detr, etc.)</a>.</p>'
|
||||||
|
other:
|
||||||
|
question: Other topic
|
||||||
|
admin:
|
||||||
|
admin_intro_html:
|
||||||
|
'<p>As an administration, you can contact us through this form. We''ll answer you as quickly as possibly by e-mail or phone.</p>
|
||||||
|
<p><strong>Caution, this form is dedicated to public bodies only.</strong>
|
||||||
|
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href="%{contact_path}">here</a>.</p>'
|
||||||
|
contact_team: Contact our team
|
||||||
|
admin_question:
|
||||||
|
question: I have a question about %{app_name}
|
||||||
|
admin_demande_rdv:
|
||||||
|
question: I request an appointment for an online presentation of %{app_name}
|
||||||
|
admin_soucis:
|
||||||
|
question: I am facing a technical issue on %{app_name}
|
||||||
|
admin_suggestion_produit:
|
||||||
|
question: I have a suggestion for an evolution
|
||||||
|
admin_demande_compte:
|
||||||
|
question: I want to open an admin account with an Orange, Wanadoo, etc. email
|
||||||
|
admin_autre:
|
||||||
|
question: Other topic
|
||||||
|
create:
|
||||||
|
direct_message_sent: Your message has been sent to the mailbox in your file.
|
||||||
|
message_sent: Your message has been sent.
|
74
config/locales/views/contact/fr.yml
Normal file
74
config/locales/views/contact/fr.yml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
fr:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
contact_form:
|
||||||
|
email: 'Votre adresse email'
|
||||||
|
email_pro: Votre adresse email professionnelle
|
||||||
|
phone: Numéro de téléphone professionnel (ligne directe)
|
||||||
|
subject: Sujet
|
||||||
|
text: Message
|
||||||
|
dossier_id: Numéro de dossier
|
||||||
|
hints:
|
||||||
|
email: 'Exemple: adresse@mail.com'
|
||||||
|
errors:
|
||||||
|
models:
|
||||||
|
contact_form:
|
||||||
|
invalid_email_format: 'est invalide'
|
||||||
|
contact:
|
||||||
|
form:
|
||||||
|
your_question: Votre question
|
||||||
|
our_answer: Notre réponse
|
||||||
|
notice_pj_product: Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer.
|
||||||
|
notice_pj_other: Une capture d’écran peut nous aider à identifier plus facilement le problème.
|
||||||
|
notice_upload_group: 'Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf.'
|
||||||
|
index:
|
||||||
|
contact: Contact
|
||||||
|
intro_html:
|
||||||
|
'<p>Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.</p>
|
||||||
|
<p>Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.</p>'
|
||||||
|
procedure_info:
|
||||||
|
question: J’ai un problème lors du remplissage de mon dossier
|
||||||
|
answer_html:
|
||||||
|
'<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
|
||||||
|
<p>Si vous avez des questions sur les informations à saisir, <a href="%{link_procedure_info}">contactez les services en charge de la démarche (FAQ)</a>.</p>'
|
||||||
|
instruction_info:
|
||||||
|
question: J’ai une question sur l’instruction de mon dossier
|
||||||
|
answer_html:
|
||||||
|
'<p>Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href="%{link_instruction_info}">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
|
||||||
|
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur l’instruction de votre dossier.</p>'
|
||||||
|
product:
|
||||||
|
question: J’ai une idée d’amélioration pour votre site
|
||||||
|
answer_html:
|
||||||
|
'<p>Une idée ? Pensez à <a href="%{link_product}" rel="noopener noreferrer" target="_blank" title="Consulter notre tableau de bord des améliorations - Nouvel onglet">consulter notre tableau de bord des améliorations</a> :</p>
|
||||||
|
<ul><li>Votez pour vos améliorations prioritaires,</li>
|
||||||
|
<li>Proposez votre propre idée.</li></ul>'
|
||||||
|
lost_user:
|
||||||
|
question: Je ne trouve pas la démarche que je veux faire
|
||||||
|
answer_html:
|
||||||
|
'<p>Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
|
||||||
|
<p>Vous pouvez aussi <a href="%{link_lost_user}">consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.)</a>.</p>'
|
||||||
|
other:
|
||||||
|
question: Autre sujet
|
||||||
|
|
||||||
|
admin:
|
||||||
|
admin_intro_html:
|
||||||
|
'<p>En tant qu’administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
|
||||||
|
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
|
||||||
|
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c''est votre cas, rendez-vous sur notre
|
||||||
|
<a href="%{contact_path}">formulaire de contact public</a>.</p>'
|
||||||
|
contact_team: Contactez notre équipe
|
||||||
|
admin_question:
|
||||||
|
question: J’ai une question sur %{app_name}
|
||||||
|
admin_demande_rdv:
|
||||||
|
question: Demande de RDV pour une présentation à distance de %{app_name}
|
||||||
|
admin_soucis:
|
||||||
|
question: J’ai un problème technique avec %{app_name}
|
||||||
|
admin_suggestion_produit:
|
||||||
|
question: J’ai une proposition d’évolution
|
||||||
|
admin_demande_compte:
|
||||||
|
question: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc.
|
||||||
|
admin_autre:
|
||||||
|
question: Autre sujet
|
||||||
|
create:
|
||||||
|
direct_message_sent: Votre message a été envoyé sur la messagerie de votre dossier.
|
||||||
|
message_sent: Votre message a été envoyé.
|
|
@ -1,53 +0,0 @@
|
||||||
en:
|
|
||||||
support:
|
|
||||||
index:
|
|
||||||
contact: Contact
|
|
||||||
intro_html:
|
|
||||||
'<p>Contact us via this form and we will answer you as quickly as possible.</p>
|
|
||||||
<p>Make sure you provide all the required information so we can help you in the best way.</p>'
|
|
||||||
notice_email: 'Expected format: address@mail.com'
|
|
||||||
your_question: Your question
|
|
||||||
our_answer: Our answer
|
|
||||||
notice_pj_product: A screenshot can help us identify the element to improve.
|
|
||||||
notice_pj_other: A screenshot can help us identify the issue.
|
|
||||||
notice_upload_group: "Maximum size: 200 MB. Supported formats: jpg, png, pdf."
|
|
||||||
procedure_info:
|
|
||||||
question: I've encountered a problem while completing my application
|
|
||||||
answer_html: "<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
|
|
||||||
<p>If you have questions about the information requested, <a href=\"%{link_procedure_info}\">contact the service in charge of the procedure (FAQ)</a>.</p>"
|
|
||||||
instruction_info:
|
|
||||||
question: I have a question about the instruction of my application
|
|
||||||
answer_html: "<p>If you have questions about the instruction of your application (response delay for example), <a href=\"%{link_instruction_info}\">contact directly the instructor via our mail system (FAQ)</a>.</p>
|
|
||||||
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
|
|
||||||
product:
|
|
||||||
question: I have an idea to improve the website
|
|
||||||
answer_html: "<p>Got an idea? <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Please check our enhancement dashboard - New tab\">Please check our enhancement dashboard</a> :</p>
|
|
||||||
<ul><li>Vote for your priority improvements</li>
|
|
||||||
<li>Share your own ideas</li></ul>"
|
|
||||||
lost_user:
|
|
||||||
question: I am having trouble finding the procedure I am looking for
|
|
||||||
answer_html: "<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
|
|
||||||
It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
|
|
||||||
<p>You can <a href=\"%{link_lost_user}\">find here the most popular procedures (licence, detr, etc.)</a>.</p>"
|
|
||||||
other:
|
|
||||||
question: Other topic
|
|
||||||
admin:
|
|
||||||
your_question: Your question
|
|
||||||
admin_intro_html: "<p>As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.</p>
|
|
||||||
<p><strong>Caution, this form is dedicated to public bodies only.</strong>
|
|
||||||
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=\"%{contact_path}\">here</a>.</p>"
|
|
||||||
contact_team: Contact our team
|
|
||||||
pro_phone_number: Professional phone number (direct line)
|
|
||||||
pro_mail: Professional email address
|
|
||||||
admin question:
|
|
||||||
question: I have a question about %{app_name}
|
|
||||||
admin demande rdv:
|
|
||||||
question: I request an appointment for an online presentation of %{app_name}
|
|
||||||
admin soucis:
|
|
||||||
question: I am facing a technical issue on %{app_name}
|
|
||||||
admin suggestion produit:
|
|
||||||
question: I have a suggestion for an evolution
|
|
||||||
admin demande compte:
|
|
||||||
question: I want to open an admin account with an Orange, Wanadoo, etc. email
|
|
||||||
admin autre:
|
|
||||||
question: Other topic
|
|
|
@ -1,53 +0,0 @@
|
||||||
fr:
|
|
||||||
support:
|
|
||||||
index:
|
|
||||||
contact: Contact
|
|
||||||
intro_html:
|
|
||||||
'<p>Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.</p>
|
|
||||||
<p>Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.</p>'
|
|
||||||
notice_email: 'Format attendu : adresse@mail.com'
|
|
||||||
your_question: Votre question
|
|
||||||
our_answer: Notre réponse
|
|
||||||
notice_pj_product: Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer.
|
|
||||||
notice_pj_other: Une capture d’écran peut nous aider à identifier plus facilement le problème.
|
|
||||||
notice_upload_group: "Taille maximale : 200 Mo. Formats supportés : jpg, png, pdf."
|
|
||||||
procedure_info:
|
|
||||||
question: J’ai un problème lors du remplissage de mon dossier
|
|
||||||
answer_html: "<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
|
|
||||||
<p>Si vous avez des questions sur les informations à saisir, <a href=\"%{link_procedure_info}\">contactez les services en charge de la démarche (FAQ)</a>.</p>"
|
|
||||||
instruction_info:
|
|
||||||
question: J’ai une question sur l’instruction de mon dossier
|
|
||||||
answer_html: "<p>Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href=\"%{link_instruction_info}\">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
|
|
||||||
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur l’instruction de votre dossier.</p>"
|
|
||||||
product:
|
|
||||||
question: J’ai une idée d’amélioration pour votre site
|
|
||||||
answer_html: "<p>Une idée ? Pensez à <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Consulter notre tableau de bord des améliorations - Nouvel onglet\">consulter notre tableau de bord des améliorations</a> :</p>
|
|
||||||
<ul><li>Votez pour vos améliorations prioritaires,</li>
|
|
||||||
<li>Proposez votre propre idée.</li></ul>"
|
|
||||||
lost_user:
|
|
||||||
question: Je ne trouve pas la démarche que je veux faire
|
|
||||||
answer_html: "<p>Nous vous invitons à contacter l’administration en charge de votre démarche pour qu’elle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
|
|
||||||
<p>Vous pouvez aussi <a href=\"%{link_lost_user}\">consulter ici la liste de nos démarches les plus fréquentes (permis, detr, etc.)</a>.</p>"
|
|
||||||
other:
|
|
||||||
question: Autre sujet
|
|
||||||
admin:
|
|
||||||
your_question: Votre question
|
|
||||||
admin_intro_html: "<p>En tant qu’administration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
|
|
||||||
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
|
|
||||||
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues d’utilité publique). Si c'est votre cas, rendez-vous sur notre
|
|
||||||
<a href=\"%{contact_path}\">formulaire de contact public</a>.</p>"
|
|
||||||
contact_team: Contactez notre équipe
|
|
||||||
pro_phone_number: Numéro de téléphone professionnel (ligne directe)
|
|
||||||
pro_mail: Adresse e-mail professionnelle
|
|
||||||
admin question:
|
|
||||||
question: J’ai une question sur %{app_name}
|
|
||||||
admin demande rdv:
|
|
||||||
question: Demande de RDV pour une présentation à distance de %{app_name}
|
|
||||||
admin soucis:
|
|
||||||
question: J’ai un problème technique avec %{app_name}
|
|
||||||
admin suggestion produit:
|
|
||||||
question: J’ai une proposition d’évolution
|
|
||||||
admin demande compte:
|
|
||||||
question: Je souhaite ouvrir un compte administrateur avec un email Orange, Wanadoo, etc.
|
|
||||||
admin autre:
|
|
||||||
question: Autre sujet
|
|
|
@ -224,10 +224,10 @@ Rails.application.routes.draw do
|
||||||
get "suivi" => "root#suivi"
|
get "suivi" => "root#suivi"
|
||||||
post "save_locale" => "root#save_locale"
|
post "save_locale" => "root#save_locale"
|
||||||
|
|
||||||
get "contact", to: "support#index"
|
get "contact", to: "contact#index"
|
||||||
post "contact", to: "support#create"
|
post "contact", to: "contact#create"
|
||||||
|
|
||||||
get "contact-admin", to: "support#admin"
|
get "contact-admin", to: "contact#admin"
|
||||||
|
|
||||||
get "mentions-legales", to: "static_pages#legal_notice"
|
get "mentions-legales", to: "static_pages#legal_notice"
|
||||||
get "declaration-accessibilite", to: "static_pages#accessibility_statement"
|
get "declaration-accessibilite", to: "static_pages#accessibility_statement"
|
||||||
|
|
17
db/migrate/20240729160650_create_contact_forms.rb
Normal file
17
db/migrate/20240729160650_create_contact_forms.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class CreateContactForms < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :contact_forms do |t|
|
||||||
|
t.string :email
|
||||||
|
t.string :subject, null: false
|
||||||
|
t.text :text, null: false
|
||||||
|
t.string :question_type, null: false
|
||||||
|
t.references :user, null: true, foreign_key: true
|
||||||
|
t.bigint :dossier_id # not a reference (dossier may not exist)
|
||||||
|
t.string :phone
|
||||||
|
t.string :tags, array: true, default: []
|
||||||
|
t.boolean :for_admin, default: false, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
18
db/schema.rb
18
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2024_07_16_091043) do
|
ActiveRecord::Schema[7.0].define(version: 2024_07_29_160650) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_buffercache"
|
enable_extension "pg_buffercache"
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -315,6 +315,21 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_16_091043) do
|
||||||
t.index ["instructeur_id"], name: "index_commentaires_on_instructeur_id"
|
t.index ["instructeur_id"], name: "index_commentaires_on_instructeur_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "contact_forms", force: :cascade do |t|
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.bigint "dossier_id"
|
||||||
|
t.string "email"
|
||||||
|
t.boolean "for_admin", default: false, null: false
|
||||||
|
t.string "phone"
|
||||||
|
t.string "question_type", null: false
|
||||||
|
t.string "subject", null: false
|
||||||
|
t.string "tags", default: [], array: true
|
||||||
|
t.text "text", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.bigint "user_id"
|
||||||
|
t.index ["user_id"], name: "index_contact_forms_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "contact_informations", force: :cascade do |t|
|
create_table "contact_informations", force: :cascade do |t|
|
||||||
t.text "adresse", null: false
|
t.text "adresse", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
@ -1229,6 +1244,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_07_16_091043) do
|
||||||
add_foreign_key "commentaires", "dossiers"
|
add_foreign_key "commentaires", "dossiers"
|
||||||
add_foreign_key "commentaires", "experts"
|
add_foreign_key "commentaires", "experts"
|
||||||
add_foreign_key "commentaires", "instructeurs"
|
add_foreign_key "commentaires", "instructeurs"
|
||||||
|
add_foreign_key "contact_forms", "users"
|
||||||
add_foreign_key "contact_informations", "groupe_instructeurs"
|
add_foreign_key "contact_informations", "groupe_instructeurs"
|
||||||
add_foreign_key "dossier_assignments", "dossiers"
|
add_foreign_key "dossier_assignments", "dossiers"
|
||||||
add_foreign_key "dossier_batch_operations", "batch_operations"
|
add_foreign_key "dossier_batch_operations", "batch_operations"
|
||||||
|
|
257
spec/controllers/contact_controller_spec.rb
Normal file
257
spec/controllers/contact_controller_spec.rb
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
describe ContactController, question_type: :controller do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
context 'signed in' do
|
||||||
|
before do
|
||||||
|
sign_in user
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
it 'should not have email field' do
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).not_to have_content("Votre adresse email")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with dossier" do
|
||||||
|
let(:user) { dossier.user }
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
|
||||||
|
it 'should fill dossier_id' do
|
||||||
|
get :index, params: { dossier_id: dossier.id }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to include((dossier.id).to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with tag" do
|
||||||
|
let(:tag) { 'yolo' }
|
||||||
|
|
||||||
|
it 'should fill tags' do
|
||||||
|
get :index, params: { tags: [tag] }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to include(tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with multiple tags" do
|
||||||
|
let(:tags) { ['yolo', 'toto'] }
|
||||||
|
|
||||||
|
it 'should fill tags' do
|
||||||
|
get :index, params: { tags: tags }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to include("value=\"yolo\"")
|
||||||
|
expect(response.body).to include("value=\"toto\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "send form" do
|
||||||
|
subject do
|
||||||
|
post :create, params: { contact_form: params }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when invisible captcha is ignored" do
|
||||||
|
let(:params) { { subject: 'bonjour', text: 'un message', question_type: 'procedure_info' } }
|
||||||
|
|
||||||
|
it 'creates a conversation on HelpScout' do
|
||||||
|
expect { subject }.to \
|
||||||
|
change(Commentaire, :count).by(0).and \
|
||||||
|
change(ContactForm, :count).by(1)
|
||||||
|
|
||||||
|
contact_form = ContactForm.last
|
||||||
|
expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form)
|
||||||
|
|
||||||
|
expect(contact_form.subject).to eq("bonjour")
|
||||||
|
expect(contact_form.text).to eq("un message")
|
||||||
|
expect(contact_form.tags).to include("procedure_info")
|
||||||
|
|
||||||
|
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
||||||
|
expect(response).to redirect_to root_path
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a drafted dossier is mentionned' do
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
let(:user) { dossier.user }
|
||||||
|
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
dossier_id: dossier.id,
|
||||||
|
question_type: ContactForm::TYPE_INSTRUCTION,
|
||||||
|
subject: 'bonjour',
|
||||||
|
text: 'un message'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a conversation on HelpScout' do
|
||||||
|
expect { subject }.to \
|
||||||
|
change(Commentaire, :count).by(0).and \
|
||||||
|
change(ContactForm, :count).by(1)
|
||||||
|
|
||||||
|
contact_form = ContactForm.last
|
||||||
|
expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form)
|
||||||
|
expect(contact_form.dossier_id).to eq(dossier.id)
|
||||||
|
|
||||||
|
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
||||||
|
expect(response).to redirect_to root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a submitted dossier is mentionned' do
|
||||||
|
let(:dossier) { create(:dossier, :en_construction) }
|
||||||
|
let(:user) { dossier.user }
|
||||||
|
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
dossier_id: dossier.id,
|
||||||
|
question_type: ContactForm::TYPE_INSTRUCTION,
|
||||||
|
subject: 'bonjour',
|
||||||
|
text: 'un message'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'posts the message to the dossier messagerie' do
|
||||||
|
expect { subject }.to change(Commentaire, :count).by(1)
|
||||||
|
assert_no_enqueued_jobs(only: HelpscoutCreateConversationJob)
|
||||||
|
|
||||||
|
expect(Commentaire.last.email).to eq(user.email)
|
||||||
|
expect(Commentaire.last.dossier).to eq(dossier)
|
||||||
|
expect(Commentaire.last.body).to include('[bonjour]')
|
||||||
|
expect(Commentaire.last.body).to include('un message')
|
||||||
|
|
||||||
|
expect(flash[:notice]).to match('Votre message a été envoyé sur la messagerie de votre dossier.')
|
||||||
|
expect(response).to redirect_to messagerie_dossier_path(dossier)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when invisible captcha is filled" do
|
||||||
|
subject do
|
||||||
|
post :create, params: {
|
||||||
|
contact_form: { subject: 'bonjour', text: 'un message', question_type: 'procedure_info' },
|
||||||
|
InvisibleCaptcha.honeypots.sample => 'boom'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a conversation on HelpScout' do
|
||||||
|
expect { subject }.not_to change(Commentaire, :count)
|
||||||
|
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'signed out' do
|
||||||
|
describe "with dossier" do
|
||||||
|
it 'should have email field' do
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to have_text("Votre adresse email")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with dossier" do
|
||||||
|
let(:tag) { 'yolo' }
|
||||||
|
|
||||||
|
it 'should fill tags' do
|
||||||
|
get :index, params: { tags: [tag] }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.body).to include(tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'send form' do
|
||||||
|
subject do
|
||||||
|
post :create, params: { contact_form: params }
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', question_type: 'procedure_info' } }
|
||||||
|
|
||||||
|
it 'creates a conversation on HelpScout' do
|
||||||
|
expect { subject }.to \
|
||||||
|
change(Commentaire, :count).by(0).and \
|
||||||
|
change(ContactForm, :count).by(1)
|
||||||
|
|
||||||
|
contact_form = ContactForm.last
|
||||||
|
expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form)
|
||||||
|
expect(contact_form.email).to eq("me@rspec.net")
|
||||||
|
|
||||||
|
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
||||||
|
expect(response).to redirect_to root_path
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when email is invalid" do
|
||||||
|
let(:params) { super().merge(email: "me@rspec") }
|
||||||
|
|
||||||
|
it 'creates a conversation on HelpScout' do
|
||||||
|
expect { subject }.not_to have_enqueued_job(HelpscoutCreateConversationJob)
|
||||||
|
expect(response.body).to include("Le champ « Votre adresse email » est invalide")
|
||||||
|
expect(response.body).to include("bonjour")
|
||||||
|
expect(response.body).to include("un message")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'contact admin' do
|
||||||
|
context 'index' do
|
||||||
|
it 'should have professionnal email field' do
|
||||||
|
get :admin
|
||||||
|
expect(response.body).to have_text('Votre adresse email professionnelle')
|
||||||
|
expect(response.body).to have_text('téléphone')
|
||||||
|
expect(response.body).to include('for_admin')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'create' do
|
||||||
|
subject do
|
||||||
|
post :create, params: { contact_form: params }
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:params) { { for_admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message', question_type: 'admin question', phone: '06' } }
|
||||||
|
|
||||||
|
describe "when form is filled" do
|
||||||
|
it "creates a conversation on HelpScout" do
|
||||||
|
expect { subject }.to change(ContactForm, :count).by(1)
|
||||||
|
|
||||||
|
contact_form = ContactForm.last
|
||||||
|
expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form)
|
||||||
|
expect(contact_form.email).to eq(params[:email])
|
||||||
|
expect(contact_form.phone).to eq("06")
|
||||||
|
expect(contact_form.tags).to match_array(["admin question", "contact form"])
|
||||||
|
|
||||||
|
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a piece justificative" do
|
||||||
|
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
|
||||||
|
let(:params) { super().merge(piece_jointe: logo) }
|
||||||
|
|
||||||
|
it "create blob and pass it to conversation job" do
|
||||||
|
expect { subject }.to change(ContactForm, :count).by(1)
|
||||||
|
|
||||||
|
contact_form = ContactForm.last
|
||||||
|
expect(contact_form.piece_jointe).to be_attached
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when invisible captcha is filled" do
|
||||||
|
subject do
|
||||||
|
post :create, params: { contact_form: params, InvisibleCaptcha.honeypots.sample => 'boom' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a conversation on HelpScout' do
|
||||||
|
subject
|
||||||
|
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,187 +0,0 @@
|
||||||
describe SupportController, type: :controller do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
context 'signed in' do
|
|
||||||
before do
|
|
||||||
sign_in user
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
|
|
||||||
it 'should not have email field' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).not_to have_content("Email *")
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with dossier" do
|
|
||||||
let(:user) { dossier.user }
|
|
||||||
let(:dossier) { create(:dossier) }
|
|
||||||
|
|
||||||
it 'should fill dossier_id' do
|
|
||||||
get :index, params: { dossier_id: dossier.id }
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to include((dossier.id).to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with tag" do
|
|
||||||
let(:tag) { 'yolo' }
|
|
||||||
|
|
||||||
it 'should fill tags' do
|
|
||||||
get :index, params: { tags: [tag] }
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to include(tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with multiple tags" do
|
|
||||||
let(:tags) { ['yolo', 'toto'] }
|
|
||||||
|
|
||||||
it 'should fill tags' do
|
|
||||||
get :index, params: { tags: tags }
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to include(tags.join(','))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "send form" do
|
|
||||||
subject do
|
|
||||||
post :create, params: params
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when invisible captcha is ignored" do
|
|
||||||
let(:params) { { subject: 'bonjour', text: 'un message' } }
|
|
||||||
|
|
||||||
it 'creates a conversation on HelpScout' do
|
|
||||||
expect { subject }.to \
|
|
||||||
change(Commentaire, :count).by(0).and \
|
|
||||||
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params))
|
|
||||||
|
|
||||||
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
|
||||||
expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when a drafted dossier is mentionned' do
|
|
||||||
let(:dossier) { create(:dossier) }
|
|
||||||
let(:user) { dossier.user }
|
|
||||||
|
|
||||||
subject do
|
|
||||||
post :create, params: {
|
|
||||||
dossier_id: dossier.id,
|
|
||||||
type: Helpscout::FormAdapter::TYPE_INSTRUCTION,
|
|
||||||
subject: 'bonjour',
|
|
||||||
text: 'un message'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a conversation on HelpScout' do
|
|
||||||
expect { subject }.to \
|
|
||||||
change(Commentaire, :count).by(0).and \
|
|
||||||
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(subject: 'bonjour', dossier_id: dossier.id))
|
|
||||||
|
|
||||||
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
|
||||||
expect(response).to redirect_to root_path(formulaire_contact_general_submitted: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when a submitted dossier is mentionned' do
|
|
||||||
let(:dossier) { create(:dossier, :en_construction) }
|
|
||||||
let(:user) { dossier.user }
|
|
||||||
|
|
||||||
subject do
|
|
||||||
post :create, params: {
|
|
||||||
dossier_id: dossier.id,
|
|
||||||
type: Helpscout::FormAdapter::TYPE_INSTRUCTION,
|
|
||||||
subject: 'bonjour',
|
|
||||||
text: 'un message'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'posts the message to the dossier messagerie' do
|
|
||||||
expect { subject }.to change(Commentaire, :count).by(1)
|
|
||||||
assert_no_enqueued_jobs(only: HelpscoutCreateConversationJob)
|
|
||||||
|
|
||||||
expect(Commentaire.last.email).to eq(user.email)
|
|
||||||
expect(Commentaire.last.dossier).to eq(dossier)
|
|
||||||
expect(Commentaire.last.body).to include('[bonjour]')
|
|
||||||
expect(Commentaire.last.body).to include('un message')
|
|
||||||
|
|
||||||
expect(flash[:notice]).to match('Votre message a été envoyé sur la messagerie de votre dossier.')
|
|
||||||
expect(response).to redirect_to messagerie_dossier_path(dossier)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when invisible captcha is filled" do
|
|
||||||
let(:params) { { subject: 'bonjour', text: 'un message', InvisibleCaptcha.honeypots.sample => 'boom' } }
|
|
||||||
it 'does not create a conversation on HelpScout' do
|
|
||||||
expect { subject }.not_to change(Commentaire, :count)
|
|
||||||
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'signed out' do
|
|
||||||
describe "with dossier" do
|
|
||||||
it 'should have email field' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to have_text("Email")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with dossier" do
|
|
||||||
let(:tag) { 'yolo' }
|
|
||||||
|
|
||||||
it 'should fill tags' do
|
|
||||||
get :index, params: { tags: [tag] }
|
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
expect(response.body).to include(tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'contact admin' do
|
|
||||||
subject do
|
|
||||||
post :create, params: params
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) { { admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message' } }
|
|
||||||
|
|
||||||
describe "when form is filled" do
|
|
||||||
it "creates a conversation on HelpScout" do
|
|
||||||
expect { subject }.to have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:admin)))
|
|
||||||
expect(flash[:notice]).to match('Votre message a été envoyé.')
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with a piece justificative" do
|
|
||||||
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
|
|
||||||
let(:params) { super().merge(piece_jointe: logo) }
|
|
||||||
|
|
||||||
it "create blob and pass it to conversation job" do
|
|
||||||
expect { subject }.to \
|
|
||||||
change(ActiveStorage::Blob, :count).by(1).and \
|
|
||||||
have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(blob_id: Integer)).and \
|
|
||||||
have_enqueued_job(VirusScannerJob)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "when invisible captcha is filled" do
|
|
||||||
let(:params) { super().merge(InvisibleCaptcha.honeypots.sample => 'boom') }
|
|
||||||
|
|
||||||
it 'does not create a conversation on HelpScout' do
|
|
||||||
subject
|
|
||||||
expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
14
spec/factories/contact_form.rb
Normal file
14
spec/factories/contact_form.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :contact_form do
|
||||||
|
user { nil }
|
||||||
|
email { 'test@example.com' }
|
||||||
|
dossier_id { nil }
|
||||||
|
subject { 'Test Subject' }
|
||||||
|
text { 'Test Content' }
|
||||||
|
question_type { 'lost_user' }
|
||||||
|
tags { ['test tag'] }
|
||||||
|
phone { nil }
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,63 +1,85 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe HelpscoutCreateConversationJob, type: :job do
|
RSpec.describe HelpscoutCreateConversationJob, type: :job do
|
||||||
let(:args) { { email: 'sender@email.com' } }
|
let(:api) { instance_double("Helpscout::API") }
|
||||||
|
let(:email) { 'help@rspec.net' }
|
||||||
|
let(:subject_text) { 'Bonjour' }
|
||||||
|
let(:text) { "J'ai un pb" }
|
||||||
|
let(:tags) { ["first tag"] }
|
||||||
|
let(:question_type) { "lost" }
|
||||||
|
let(:phone) { nil }
|
||||||
|
let(:contact_form) { create(:contact_form, email:, subject: subject_text, text:, tags:, phone:, question_type:) }
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
context 'when blob_id is not present' do
|
before do
|
||||||
it 'sends the form without a file' do
|
allow(Helpscout::API).to receive(:new).and_return(api)
|
||||||
form_adapter = double('Helpscout::FormAdapter')
|
allow(api).to receive(:create_conversation)
|
||||||
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob: nil))).and_return(form_adapter)
|
.and_return(double(
|
||||||
expect(form_adapter).to receive(:send_form)
|
success?: true,
|
||||||
|
headers: { 'Resource-ID' => 'new-conversation-id' }
|
||||||
|
))
|
||||||
|
allow(api).to receive(:add_tags)
|
||||||
|
allow(api).to receive(:add_phone_number) if phone.present?
|
||||||
|
end
|
||||||
|
|
||||||
described_class.perform_now(**args)
|
subject {
|
||||||
|
described_class.perform_now(contact_form)
|
||||||
|
}
|
||||||
|
|
||||||
|
context 'when no file is attached' do
|
||||||
|
it 'sends the form without a file' do
|
||||||
|
subject
|
||||||
|
expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil)
|
||||||
|
expect(api).to have_received(:add_tags).with("new-conversation-id", match_array(tags.concat(["contact form", question_type])))
|
||||||
|
expect(contact_form).to be_destroyed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when blob_id is present' do
|
context 'when a file is attached' do
|
||||||
let(:blob) {
|
|
||||||
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png")
|
|
||||||
}
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: pending, safe?: safe))
|
file = fixture_file_upload('spec/fixtures/files/white.png', 'image/png')
|
||||||
|
contact_form.piece_jointe.attach(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the file has not been scanned yet' do
|
context 'when the file has not been scanned yet' do
|
||||||
let(:pending) { true }
|
before do
|
||||||
let(:safe) { false }
|
allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: true, safe?: false))
|
||||||
|
end
|
||||||
|
|
||||||
it 'reenqueue job' do
|
it 'reenqueues job' do
|
||||||
expect {
|
expect { subject }.to have_enqueued_job(described_class).with(contact_form)
|
||||||
described_class.perform_now(blob_id: blob.id, **args)
|
|
||||||
}.to have_enqueued_job(described_class).with(blob_id: blob.id, **args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the file is safe' do
|
context 'when the file is safe' do
|
||||||
let(:pending) { false }
|
before do
|
||||||
let(:safe) { true }
|
allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: true))
|
||||||
|
end
|
||||||
|
|
||||||
it 'downloads the file and sends the form' do
|
it 'sends the form with the file' do
|
||||||
form_adapter = double('Helpscout::FormAdapter')
|
subject
|
||||||
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob:))).and_return(form_adapter)
|
expect(api).to have_received(:create_conversation).with(email, subject_text, text, contact_form.piece_jointe)
|
||||||
allow(form_adapter).to receive(:send_form)
|
|
||||||
|
|
||||||
described_class.perform_now(blob_id: blob.id, **args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the file is not safe' do
|
context 'when the file is not safe' do
|
||||||
let(:pending) { false }
|
before do
|
||||||
let(:safe) { false }
|
allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: false))
|
||||||
|
|
||||||
it 'downloads the file and sends the form' do
|
|
||||||
form_adapter = double('Helpscout::FormAdapter')
|
|
||||||
allow(Helpscout::FormAdapter).to receive(:new).with(hash_including(args.merge(blob: nil))).and_return(form_adapter)
|
|
||||||
allow(form_adapter).to receive(:send_form)
|
|
||||||
|
|
||||||
described_class.perform_now(blob_id: blob.id, **args)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'ignores the file' do
|
||||||
|
subject
|
||||||
|
expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a phone' do
|
||||||
|
let(:phone) { "06" }
|
||||||
|
|
||||||
|
it 'associates the phone number' do
|
||||||
|
subject
|
||||||
|
expect(api).to have_received(:add_phone_number).with(email, phone)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
describe Helpscout::FormAdapter do
|
|
||||||
describe '#send_form' do
|
|
||||||
let(:api) { spy(double(:api)) }
|
|
||||||
|
|
||||||
context 'create_conversation' do
|
|
||||||
before do
|
|
||||||
allow(api).to receive(:create_conversation)
|
|
||||||
.and_return(double(success?: true, headers: {}))
|
|
||||||
described_class.new(params, api).send_form
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) {
|
|
||||||
{
|
|
||||||
email: email,
|
|
||||||
subject: subject,
|
|
||||||
text: text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let(:email) { 'paul.chavard@beta.gouv.fr' }
|
|
||||||
let(:subject) { 'Bonjour' }
|
|
||||||
let(:text) { "J'ai un problem" }
|
|
||||||
|
|
||||||
it 'should call method' do
|
|
||||||
expect(api).to have_received(:create_conversation)
|
|
||||||
.with(email, subject, text, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'add_tags' do
|
|
||||||
before do
|
|
||||||
allow(api).to receive(:create_conversation)
|
|
||||||
.and_return(
|
|
||||||
double(
|
|
||||||
success?: true,
|
|
||||||
headers: {
|
|
||||||
'Resource-ID' => conversation_id
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
described_class.new(params, api).send_form
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) {
|
|
||||||
{
|
|
||||||
email: email,
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
tags: tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let(:email) { 'paul.chavard@beta.gouv.fr' }
|
|
||||||
let(:subject) { 'Bonjour' }
|
|
||||||
let(:text) { "J'ai un problem" }
|
|
||||||
let(:tags) { ['info demarche'] }
|
|
||||||
let(:conversation_id) { '123' }
|
|
||||||
|
|
||||||
it 'should call method' do
|
|
||||||
expect(api).to have_received(:create_conversation)
|
|
||||||
.with(email, subject, text, nil)
|
|
||||||
expect(api).to have_received(:add_tags)
|
|
||||||
.with(conversation_id, tags + ['contact form'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'add_phone' do
|
|
||||||
before do
|
|
||||||
allow(api).to receive(:create_conversation)
|
|
||||||
.and_return(
|
|
||||||
double(
|
|
||||||
success?: true,
|
|
||||||
headers: {
|
|
||||||
'Resource-ID' => conversation_id
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
described_class.new(params, api).send_form
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:params) {
|
|
||||||
{
|
|
||||||
email: email,
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
phone: '0666666666'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let(:phone) { '0666666666' }
|
|
||||||
let(:email) { 'paul.chavard@beta.gouv.fr' }
|
|
||||||
let(:subject) { 'Bonjour' }
|
|
||||||
let(:text) { "J'ai un problem" }
|
|
||||||
let(:tags) { ['info demarche'] }
|
|
||||||
let(:conversation_id) { '123' }
|
|
||||||
|
|
||||||
it 'should call method' do
|
|
||||||
expect(api).to have_received(:create_conversation)
|
|
||||||
.with(email, subject, text, nil)
|
|
||||||
expect(api).to have_received(:add_phone_number)
|
|
||||||
.with(email, phone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue