Merge pull request #1429 from betagouv/satisfaction-v2

Nouvelle interface pour la satisfaction usager
intérim
Johan Girod 2021-02-26 19:03:49 +01:00 committed by GitHub
commit e8309b19b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 259 additions and 216 deletions

View File

@ -33,7 +33,9 @@ export interface ATTracker {
set(infos: ATHit): void
}
click: {
set(infos: ATHit): void
set(
infos: ATHit & { type: 'exit' | 'download' | 'action' | 'navigation' }
): void
}
customVars: {
set(variables: CustomVars): void

View File

@ -1,24 +1,3 @@
.feedback-page {
display: flex;
justify-content: center;
flex-wrap: wrap;
padding-top: 0.6rem;
padding-bottom: 0.6rem;
background: var(--lightestColor);
border-radius: 0.9rem;
padding: 0.6rem 1rem;
margin: 1rem 0;
}
.feedback-page button.link-button {
margin: 0 0.6rem;
}
@media (min-width: 1200px) {
.feedback-page .feedbackButtons {
display: inline;
}
}
.zammad-form > .form-group:nth-of-type(2) {
display: none;
}

View File

@ -1,14 +1,12 @@
import { ScrollToElement } from 'Components/utils/Scroll'
import { TrackerContext } from 'Components/utils/withTracker'
import React, { useContext, useEffect, useRef } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router'
type Props = { onEnd: () => void; onCancel: () => void }
declare global {
const $: any
}
export default function FeedbackForm({ onEnd, onCancel }: Props) {
export default function FeedbackForm() {
// const tracker = useContext(TrackerContext)
const pathname = useLocation().pathname
const page = pathname.split('/').slice(-1)[0]
@ -29,12 +27,12 @@ export default function FeedbackForm({ onEnd, onCancel }: Props) {
isSimulateur ? 'le simulateur' : 'la page'
} ${page}`,
messageSubmit: 'Envoyer',
messageThankYou:
'Merci pour votre retour ! Vous pouvez aussi nous contacter directement à contact@mon-entreprise.beta.gouv.fr',
messageThankYou: 'Merci de votre retour !',
lang,
attributes: [
{
display: 'Message',
display:
"Que pouvons-nous améliorer afin de mieux répondre à vos attentes ? (ne pas mettre d'informations personnelles)",
name: 'body',
tag: 'textarea',
placeholder: 'Your Message...',
@ -49,7 +47,7 @@ export default function FeedbackForm({ onEnd, onCancel }: Props) {
defaultValue: '-',
},
{
display: 'Email (pour recevoir notre réponse)',
display: 'Email (pour recevoir une réponse)',
name: 'email',
tag: 'input',
type: 'email',
@ -61,29 +59,10 @@ export default function FeedbackForm({ onEnd, onCancel }: Props) {
script.src = 'https://mon-entreprise.zammad.com/assets/form/form.js'
document.body.appendChild(script)
}, 100)
// tracker.push(['trackEvent', 'Feedback', 'written feedback submitted'])
}, [])
return (
<ScrollToElement onlyIfNotVisible>
<div style={{ textAlign: 'end' }}>
<button
onClick={() => onCancel()}
className="ui__ link-button"
style={{ textDecoration: 'none', marginLeft: '0.3rem' }}
aria-label="close"
>
X
</button>
</div>
<p>
<Trans i18nKey="feedback.bad.form.headline">
Votre retour nous est précieux afin d'améliorer ce site en continu.
Sur quoi devrions nous travailler afin de mieux répondre à vos
attentes ?
</Trans>
</p>
<div id="feedback-form"></div>
</ScrollToElement>
)

View File

@ -1,145 +0,0 @@
import { TrackerContext } from 'Components/utils/withTracker'
import React, { useCallback, useContext, useState } from 'react'
import { Trans } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import { TrackingContext } from '../../ATInternetTracking'
import safeLocalStorage from '../../storage/safeLocalStorage'
import './Feedback.css'
import Form from './FeedbackForm'
type PageFeedbackProps = {
blacklist?: Array<string>
customMessage?: React.ReactNode
customEventName?: string
}
const localStorageKey = (feedback: [string, string]) =>
`app::feedback::v2::${feedback.join('::')}`
const saveFeedbackOccurrenceInLocalStorage = ([name, path, rating]: [
string,
string,
number
]) => {
safeLocalStorage.setItem(
localStorageKey([name, path]),
JSON.stringify(rating)
)
}
const feedbackAlreadyGiven = (feedback: [string, string]) => {
return !!safeLocalStorage.getItem(localStorageKey(feedback))
}
export default function PageFeedback({
customMessage,
customEventName,
}: PageFeedbackProps) {
const location = useLocation()
const tracker = useContext(TrackerContext)
const [state, setState] = useState({
showForm: false,
showThanks: false,
feedbackAlreadyGiven: feedbackAlreadyGiven([
customEventName || 'rate page usefulness',
location.pathname,
]),
})
const ATTracker = useContext(TrackingContext)
const handleFeedback = useCallback(({ useful }: { useful: boolean }) => {
tracker.push([
'trackEvent',
'Feedback',
useful ? 'positive rating' : 'negative rating',
location.pathname,
])
ATTracker.click.set({
chapter1: 'satisfaction',
name: useful ? 'positive' : 'negative',
})
ATTracker.dispatch()
const feedback = [
customEventName || 'rate page usefulness',
location.pathname,
useful ? 10 : 0.1,
] as [string, string, number]
tracker.push(['trackEvent', 'Feedback', ...feedback])
saveFeedbackOccurrenceInLocalStorage(feedback)
setState({
showThanks: useful,
feedbackAlreadyGiven: true,
showForm: !useful,
})
}, [])
const handleErrorReporting = useCallback(() => {
tracker.push(['trackEvent', 'Feedback', 'report error', location.pathname])
setState({ ...state, showForm: true })
}, [])
if (state.feedbackAlreadyGiven && !state.showForm && !state.showThanks) {
return null
}
return (
<div
className="ui__ container"
style={{ display: 'flex', justifyContent: 'center' }}
>
<div className="feedback-page ui__ notice ">
{!state.showForm && !state.showThanks && (
<>
<div style={{ flexShrink: 0 }}>
{customMessage || (
<Trans i18nKey="feedback.question">
Cette page vous est utile ?
</Trans>
)}{' '}
</div>
<div className="feedbackButtons">
<button
className="ui__ link-button"
onClick={() => handleFeedback({ useful: true })}
>
<Trans>Oui</Trans>
</button>{' '}
<button
className="ui__ link-button"
onClick={() => handleFeedback({ useful: false })}
>
<Trans>Non</Trans>
</button>
<button
className="ui__ link-button"
onClick={handleErrorReporting}
>
<Trans i18nKey="feedback.reportError">
Faire une suggestion
</Trans>
</button>
</div>
</>
)}
{state.showThanks && (
<div>
<Trans i18nKey="feedback.thanks">
Merci pour votre retour ! Vous pouvez nous contacter directement à{' '}
<a href="mailto:contact@mon-entreprise.beta.gouv.fr">
contact@mon-entreprise.beta.gouv.fr
</a>
</Trans>
</div>
)}
{state.showForm && (
<Form
onEnd={() =>
setState({ ...state, showThanks: true, showForm: false })
}
onCancel={() =>
setState({ ...state, showThanks: false, showForm: false })
}
/>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,157 @@
import React, { useCallback, useContext, useState } from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components'
import { TrackingContext } from '../../ATInternetTracking'
import safeLocalStorage from '../../storage/safeLocalStorage'
import './Feedback.css'
import Form from './FeedbackForm'
type PageFeedbackProps = {
blacklist?: Array<string>
customMessage?: React.ReactNode
customEventName?: string
}
const localStorageKey = (url: string) => `app::feedback::v3::${url}`
const setFeedbackGivenForUrl = (url: string) => {
safeLocalStorage.setItem(
localStorageKey(url),
JSON.stringify(new Date().toISOString())
)
}
// Ask for feedback again after 4 month
const askFeedback = (url: string) => {
const previousFeedbackDate = safeLocalStorage.getItem(localStorageKey(url))
if (!previousFeedbackDate) {
return true
}
return (
new Date(previousFeedbackDate) <
new Date(new Date().setMonth(new Date().getMonth() - 4))
)
}
export default function PageFeedback({ customMessage }: PageFeedbackProps) {
const url = useLocation().pathname
const [display, setDisplay] = useState(askFeedback(url))
const [state, setState] = useState({
showForm: false,
showThanks: false,
})
const ATTracker = useContext(TrackingContext)
const handleFeedback = useCallback(
(rating: 'mauvais' | 'moyen' | 'bien' | 'très bien') => {
setFeedbackGivenForUrl(url)
ATTracker.click.set({
chapter1: 'satisfaction',
type: 'action',
name: rating,
})
ATTracker.dispatch()
const askDetails = ['mauvais', 'moyen'].includes(rating)
setState({
showThanks: !askDetails,
showForm: askDetails,
})
},
[ATTracker, url]
)
const handleErrorReporting = useCallback(() => {
setState({ ...state, showForm: true })
}, [state])
if (!display) {
return null
}
return (
<div
className="ui__ notice"
style={{
padding: '1rem 0',
position: 'relative',
}}
>
{!state.showForm && !state.showThanks && (
<div style={{ textAlign: 'center' }}>
<p>
{customMessage || (
<Trans i18nKey="feedback.question">
Êtes-vous satisfait de cette page ?
</Trans>
)}{' '}
</p>
<div
css={`
display: flex;
flex-wrap: wrap;
justify-content: center;
`}
>
<div>
<EmojiButton onClick={() => handleFeedback('mauvais')}>
{emoji('🙁')}
</EmojiButton>
<EmojiButton onClick={() => handleFeedback('moyen')}>
{emoji('😐')}
</EmojiButton>
</div>
<div>
<EmojiButton onClick={() => handleFeedback('bien')}>
{emoji('🙂')}
</EmojiButton>
<EmojiButton onClick={() => handleFeedback('très bien')}>
{emoji('😀')}
</EmojiButton>
</div>
</div>
<button
className="ui__ simple small button"
onClick={handleErrorReporting}
>
<Trans i18nKey="feedback.reportError">Faire une suggestion</Trans>
</button>
</div>
)}
{(state.showThanks || state.showForm) && (
<button
onClick={() => setDisplay(false)}
style={{
position: 'absolute',
right: '-2.4rem',
top: '-0.6rem',
fontSize: '200%',
}}
css={`
:hover {
opacity: 0.8;
}
`}
aria-label="close"
>
<small>×</small>
</button>
)}
{state.showThanks && (
<div style={{ marginRight: '1.2rem' }}>
<Trans i18nKey="feedback.thanks">Merci de votre retour !</Trans>
</div>
)}
{state.showForm && <Form />}
</div>
)
}
const EmojiButton = styled.button`
font-size: 1.5rem;
padding: 0.6rem;
transition: transform 0.05s;
will-change: transform;
:hover {
transform: scale(1.3);
}
`

View File

@ -1,7 +1,7 @@
import Conversation, {
ConversationProps,
} from 'Components/conversation/Conversation'
import PageFeedback from 'Components/Feedback/PageFeedback'
import PageFeedback from 'Components/Feedback'
import SearchButton from 'Components/SearchButton'
import ShareSimulationBanner from 'Components/ShareSimulationBanner'
import TargetSelection from 'Components/TargetSelection'
@ -38,6 +38,7 @@ export default function Simulation({
return (
<>
{simulationBloc}
<SearchButton invisibleButton />
{!firstStepCompleted && <TrackPage name="accueil" />}
{firstStepCompleted && (
@ -47,15 +48,67 @@ export default function Simulation({
<ShareSimulationBanner />
<Questions customEndMessages={customEndMessages} />
<br />
<PageFeedback
customMessage={
<Trans i18nKey="feedback.simulator">
Êtes-vous satisfait de ce simulateur ?
</Trans>
}
customEventName="rate simulator"
/>
{explanations}
<div className="ui__ full-width">
<div
css={`
display: flex;
flex-direction: column;
align-items: center;
@media (min-width: 1200px) {
flex-direction: row;
align-items: baseline;
}
`}
>
{explanations && (
<>
<div
css={`
flex: 1;
`}
/>
<div
className="ui__ container"
css={`
flex-shrink: 0;
`}
>
{explanations}
</div>
</>
)}
<div
css={`
margin-top: 1rem;
@media (min-width: 1200px) {
flex-grow: 0;
flex-shrink: 1;
flex: 1;
display: flex;
${!explanations && 'justify-content: center;'}
position: sticky;
top: 1rem;
}
`}
>
<div
css={`
margin: 0 1rem;
`}
>
<div className="ui__ card lighter-bg">
<PageFeedback
customMessage={
<Trans i18nKey="feedback.simulator">
Êtes-vous satisfait de ce simulateur&nbsp;?
</Trans>
}
/>
</div>
</div>
</div>
</div>
</div>
</Animate.fromTop>
</>
)}

View File

@ -57,7 +57,9 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
return currentQuestion ? (
<>
<TrackPage name="simulation commencée" />
{Object.keys(situation).length !== 0 && (
<TrackPage name="simulation commencée" />
)}
<Aide />
<div style={{ outline: 'none' }} onKeyDown={handleKeyDown}>
<Animate.fadeIn>

View File

@ -1,5 +1,7 @@
.footer {
margin-top: 5rem;
margin-left: -1rem;
margin-right: -1rem;
}
.footer img {
height: 2.5rem;

View File

@ -1,4 +1,4 @@
import PageFeedback from 'Components/Feedback/PageFeedback'
import PageFeedback from 'Components/Feedback'
import LegalNotice from 'Components/LegalNotice'
import NewsletterRegister from 'Components/NewsletterRegister'
import SocialIcon from 'Components/ui/SocialIcon'
@ -31,6 +31,8 @@ const useShowFeedback = () => {
return ![
sitePath.index,
...Object.values(simulators).map((s) => s.path),
'',
'/',
].includes(currentPath)
}
export default function Footer() {
@ -58,7 +60,19 @@ export default function Footer() {
))}
</Helmet>
<footer className="footer">
{showFeedback && <PageFeedback />}
{showFeedback && (
<div>
<div
className="ui__ lighter-bg"
css={`
display: flex;
justify-content: center;
`}
>
<PageFeedback />
</div>
</div>
)}
<div className="ui__ container">
<NewsletterRegister />
<hr className="footer__separator" />

View File

@ -1,6 +1,7 @@
import Conversation from 'Components/conversation/Conversation'
import { HiddenOptionContext } from 'Components/conversation/Question'
import Animate from 'Components/ui/animate'
import Warning from 'Components/ui/WarningBlock'
import { ThemeColorsContext } from 'Components/utils/colors'
import Emoji from 'Components/utils/Emoji'
import { useEngine } from 'Components/utils/EngineContext'
@ -8,7 +9,6 @@ import { SitePathsContext } from 'Components/utils/SitePathsContext'
import { useSimulationProgress } from 'Components/utils/useNextQuestion'
import { useParamsFromSituation } from 'Components/utils/useSearchParamsSimulationSharing'
import useSimulationConfig from 'Components/utils/useSimulationConfig'
import Warning from 'Components/ui/WarningBlock'
import { DottedName } from 'modele-social'
import Engine, { formatValue } from 'publicodes'
import { partition } from 'ramda'
@ -17,6 +17,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { SimulationConfig, Situation } from 'Reducers/rootReducer'
import styled from 'styled-components'
import { TrackPage } from '../../ATInternetTracking'
type AideDescriptor = {
title: string
@ -219,6 +220,7 @@ function Results() {
return progress === 0 ? (
<>
<TrackPage name="accueil" />
<h3>
<Trans i18nKey="pages.simulateurs.aides-embauche.titres.aides">
Les aides

View File

@ -42,7 +42,7 @@
from="/s%C3%A9curit%C3%A9-sociale/auto-entrepreneur"
to="/simulateurs/auto-entrepreneur"
status = 301
# FR | sécurité-social/salarié -> simulateurs/salaire-brut-net
[[redirects]]
from="/s%C3%A9curit%C3%A9-sociale/salari%C3%A9"
@ -123,7 +123,7 @@
status = 200
# Publicode
# Publicode
[[redirects]]
from = "https://publi.codes/*"
to = "/publicodes.html"
@ -166,7 +166,7 @@
# Mon-entreprise.fr
# Mon-entreprise.fr
[[redirects]]
from = "https://mon-entreprise.fr/robots.txt"
to = "/robots.infrance.txt"
@ -257,9 +257,7 @@
[context.production.environment]
EN_SITE = "https://mycompanyinfrance.fr${path}"
FR_SITE = "https://mon-entreprise.fr${path}"
AT_INTERNET_SITE_ID = "617189"
# Todo replace with prod env when sure about the metrics
# AT_INTERNET_SITE_ID = 617190
AT_INTERNET_SITE_ID = "617190"
[context."demo".environment]
EN_SITE = "https://demo.mon-entreprise.fr${path}?s=m"
@ -267,4 +265,4 @@
[context."next".environment]
EN_SITE = "https://next.mon-entreprise.fr${path}?s=m"
FR_SITE = "https://next.mon-entreprise.fr${path}"
FR_SITE = "https://next.mon-entreprise.fr${path}"