Ajoute un module de retour sur infrance

pull/374/head
Johan Girod 2018-09-21 17:28:28 +02:00
parent b7ebfafcbe
commit 0606759e74
18 changed files with 372 additions and 174 deletions

View File

@ -136,6 +136,13 @@
</div>
<!-- A little help for the Netlify bots if you're not using a SSG -->
<form name="feedback" netlify netlify-honeypot="bot-field" hidden>
<textarea name="message"></textarea>
<input type="email" name="email" />
</form>
<!-- OUTDATED BROWSER WARNING -->
<div id="outdated-browser" style="position: fixed; top: 0; left: 0; bottom: 0; right: 0; display: none;">
<h2 style="margin: 100px auto; max-width: 800px; text-align: center; font-size: 40px; font-family: 'IBM Plex Sans', sans-serif; font-weight: 300;">

View File

@ -128,6 +128,7 @@
"sinon": "^4.5.0",
"sinon-chai": "^3.0.0",
"source-map-support": "^0.5.4",
"static-site-generator-webpack-plugin": "^3.4.1",
"style-loader": "^0.20.3",
"url-loader": "^1.0.1",
"webpack": "^4.20.1",

View File

@ -0,0 +1,18 @@
.feedback-page {
align-items: flex-start;
display: none;
justify-content: center;
padding: 0.6rem !important;
}
.feedback-page.visible {
display: flex;
}
@keyframes flip-in {
from {
}
to {
transform: rotateX(0);
opacity: 1;
}
}

View File

@ -0,0 +1,189 @@
/* @flow */
import withTracker from 'Components/utils/withTracker'
import React, { Component } from 'react'
import { Trans } from 'react-i18next'
import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import type { Tracker } from 'Components/utils/withTracker'
import type { Location } from 'react-router-dom'
type Props = { onEnd: () => void, location: Location, tracker: Tracker }
type State = {
formStep: 1 | 2 | null
}
type Explanation =
| 'NOT UNDERSTANDABLE'
| 'NOT RELEVANT'
| 'POOR CONTENT'
| 'OTHER'
class FeedbackForm extends Component<Props, State> {
state = {
formStep: 1
}
formRef: ?HTMLFormElement
handleFormSubmit = e => {
this.props.tracker.push([
'trackEvent',
'Feedback',
'written feedback submitted'
])
e.preventDefault()
// $FlowFixMe
fetch('/', {
method: 'POST',
// $FlowFixMe
body: new FormData(this.formRef)
})
this.handleClose()
}
handleClose = () => {
this.setState({ formStep: null })
this.props.onEnd()
}
handleExplanationGiven = (event: { target: { value: Explanation } }) => {
this.props.tracker.push([
'trackEvent',
'Feedback',
`bad rating explained: ${event.target.value}`,
this.props.location.pathname
])
this.setState({ formStep: 2 })
}
render() {
return (
<>
{this.state.formStep === 1 && (
<fieldset>
<legend>
<p>
<strong>
<Trans i18nKey="feedback.bad.headline">
Nous sommes désolé de ne pas vous avoir apporté entière
satisfaction.
</Trans>
</strong>{' '}
<Trans i18nKey="feedback.bad.question">
Quel a été le principal problème ?
</Trans>
</p>
</legend>
<div>
<input
type="radio"
name="badRatingReason"
id="notUnderstandable"
value="NOT UNDERSTANDABLE"
onChange={this.handleExplanationGiven}
/>
<label htmlFor="notUnderstandable">
<Trans i18nKey="feedback.bad.answer.notUnderstandable">
Cette page manque de clarté
</Trans>
</label>
</div>
<div>
<input
type="radio"
name="badRatingReason"
id="notRelevant"
value="NOT RELEVANT"
onChange={this.handleExplanationGiven}
/>
<label htmlFor="notRelevant">
<Trans i18nKey="feedback.bad.answer.notRelevant">
Cette page a un contenu qui ne s'applique pas à ma situation
</Trans>
</label>
</div>
<div>
<input
type="radio"
name="badRatingReason"
id="poorContent"
value="POOR CONTENT"
onChange={this.handleExplanationGiven}
/>
<label htmlFor="poorContent">
<Trans i18nKey="feedback.bad.answer.poorContent">
Je ne trouve pas ce que je suis venu chercher
</Trans>
</label>
</div>
<div>
<input
type="radio"
name="badRatingReason"
id="other"
onChange={this.handleExplanationGiven}
value="OTHER"
/>
<label htmlFor="other">
<Trans i18nKey="feedback.bad.answer.other">Autre</Trans>
</label>
</div>
<br />
</fieldset>
)}
{this.state.formStep === 2 && (
<div>
<div style={{ textAlign: 'end' }}>
<button
onClick={this.handleClose}
className="ui__ link-button"
style={{ textDecoration: 'none', marginLeft: '0.3rem' }}
aria-label="close">
X
</button>
</div>
<form
name="feedback"
style={{ flex: 1 }}
method="post"
ref={ref => (this.formRef = ref)}
onSubmit={this.handleFormSubmit}>
<input type="hidden" name="form-name" value="feedback" />
<label htmlFor="message">
<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>
</label>
<textarea
name="message"
rows="5"
style={{ resize: 'none', width: '100%', padding: '0.6rem' }}
/>
<br />
<br />
<label htmlFor="email">
<p>
<Trans i18nKey="feedback.bad.form.email">
Votre email (si vous souhaitez une réponse de notre part)
</Trans>
</p>
</label>
<input type="email" name="email" />
<br />
<p>
<button className="ui__ button small" type="submit">
<Trans>Envoyer</Trans>
</button>
</p>
</form>
</div>
)}
</>
)
}
}
export default compose(
withTracker,
withRouter
)(FeedbackForm)

View File

@ -0,0 +1,84 @@
/* @flow */
import classnames from 'classnames'
import withTracker from 'Components/utils/withTracker'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import './Feedback.css'
import Form from './FeedbackForm'
import type { Tracker } from 'Components/utils/withTracker'
import type { Location } from 'react-router-dom'
type Props = {
location: Location,
tracker: Tracker
}
type State = {
useful: ?boolean,
visible: boolean
}
class PageFeedback extends Component<Props, State> {
state = {
useful: null,
visible: true
}
handleClose = () => {
this.setState({ visible: false })
}
handleFeedback = ({ useful }) => {
this.props.tracker.push([
'trackEvent',
'Feedback',
'page usefulness rated',
this.props.location.pathname,
useful ? 10 : 0
])
this.setState({ useful })
}
handleErrorReporting = () => {}
render() {
return (
<div
className={classnames(
'feedback-page',
'ui__ container notice',
this.state.visible && 'visible'
)}>
{this.state.useful === null ? (
<>
<Trans i18nKey="feedback.question">
Cette page vous a-t-elle été utile ?
</Trans>{' '}
<button
style={{ marginLeft: '0.6rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: true })}>
<Trans>Oui</Trans>
</button>{' '}
<button
style={{ marginLeft: '0.6rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: false })}>
<Trans>Non</Trans>
</button>
</>
) : this.state.useful === true ? (
<Trans i18nKey="feedback.thanks">Merci pour votre retour !</Trans>
) : (
/* this.state.useful === false ? */
<Form onEnd={this.handleClose} />
)}
</div>
)
}
}
export default compose(
translate(),
withRouter,
withTracker
)(PageFeedback)

View File

@ -1,5 +1,5 @@
#RulePage {
margin-top: 1rem;
margin-top: 2rem;
}
.rule-page__header {
display: flex;

View File

@ -26,7 +26,7 @@ import SearchButton from './SearchButton'
flatRules: flatRulesSelector(state)
}))
@translate()
export default class RulePage extends Component {
class RulePage extends Component {
render() {
let { flatRules } = this.props,
name = path(['match', 'params', 'name'], this.props),
@ -48,7 +48,7 @@ export default class RulePage extends Component {
}
renderRule(dottedName) {
return (
<div id="RulePage" className="ui__ container">
<div id="RulePage">
<ScrollToTop />
<div className="rule-page__header">
<BackToSimulation
@ -112,3 +112,5 @@ let DisambiguateRuleQuery = ({ rules, flatRules }) => (
</ul>
</div>
)
export default RulePage

View File

@ -1,13 +0,0 @@
.satisfaction-smiley {
font-size: 90%;
font-weight: bold;
border-radius: 6em;
width: 2em;
text-align: center;
display: inline-block;
border-width: 2px;
border-style: solid;
margin: 0 0.6em;
padding: 0.3em 0;
transform: rotate(90deg);
}

View File

@ -1,29 +0,0 @@
import HoverDecorator from 'Components/utils/HoverDecorator'
import withColours from 'Components/utils/withColours'
import React, { Component } from 'react'
import './SatisfactionSmiley.css'
@withColours
@HoverDecorator
export default class SatisfactionSmiley extends Component {
render() {
return (
<button
onClick={() => this.props.onClick(this.props.text)}
className="satisfaction-smiley"
style={
this.props.hover
? {
background: this.props.colours.colour,
color: 'white',
borderColor: 'transparent'
}
: {
color: this.props.colours.colour,
borderColor: this.props.colours.colour
}
}>
{this.props.text}
</button>
)
}
}

View File

@ -21,7 +21,6 @@ import PaySlip from './PaySlip'
import QuickLink from './QuickLink'
import ResultView from './ResultView'
import './Simu.css'
import Sondage from './Sondage'
import TargetSelection from './TargetSelection'
@withColours
@ -43,7 +42,7 @@ import TargetSelection from './TargetSelection'
})
)
@withLanguage
export default class Simu extends Component {
class Simu extends Component {
state = {
displayPreviousAnswers: false
}
@ -141,9 +140,7 @@ export default class Simu extends Component {
{conversationStarted && (
<Animate.fromBottom>
<ResultView />
<div style={{ textAlign: 'center' }}>
<Sondage />
</div>
<div style={{ textAlign: 'center' }} />
</Animate.fromBottom>
)}
{displayPreviousAnswers && (
@ -183,10 +180,11 @@ export default class Simu extends Component {
<Trans>Fiche de paie</Trans>
</h2>
<PaySlip />
<Sondage />
</Animate.fromBottom>
)}
</>
)
}
}
export default Simu

View File

@ -1,93 +0,0 @@
import withLanguage from 'Components/utils/withLanguage'
import withTracker from 'Components/utils/withTracker'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { connect } from 'react-redux'
import {
nextStepsSelector,
noUserInputSelector
} from 'Selectors/analyseSelectors'
import Smiley from './SatisfactionSmiley'
@connect(state => ({
conversationStarted: state.conversationStarted,
noUserInput: noUserInputSelector(state),
nextSteps: nextStepsSelector(state)
}))
@translate()
@withLanguage
@withTracker
export default class Sondage extends Component {
state = {
visible: true,
showForm: false,
askFeedbackTime: 'AFTER_FIRST_ESTIMATE'
}
handleClose = () => {
this.setState({ visible: false })
}
onSmileyClick = satisfaction => {
this.props.tracker.push(['trackEvent', 'feedback', 'smiley', satisfaction])
this.setState({ showForm: true, satisfaction, visible: false })
}
render() {
let { satisfaction, showForm, visible } = this.state
return (
<div className="sondage__container">
{showForm &&
(satisfaction === ':|' ? (
<p className="ui__ notice">
<strong>
<Trans i18nKey="feedback.bad.headline">
Nous sommes désolé de ne pas vous avoir apporté entière
satisfaction.
</Trans>
</strong>{' '}
<Trans i18nKey="feedback.bad.support">
Si vous le souhaitez, vous pouvez nous envoyer un mail à{' '}
<a href="mailto:contact@embauche.beta.gouv.fr">
contact@embauche.beta.gouv.fr
</a>
Nous vous garantissons de vous répondre au plus vite, et de
faire tout notre possible pour vous venir en aide
</Trans>
</p>
) : (
<p className="ui__ notice">
<strong>
<Trans i18nKey="feedback.good.headline">
Merci pour votre retour !
</Trans>
</strong>{' '}
<Trans i18nKey="feedback.good.support">
Si vous avez une remarque, ou une idée d'amélioration, n'hésitez
pas à nous contacter directement par mail à
<a href="mailto:contact@embauche.beta.gouv.fr">
contact@embauche.beta.gouv.fr
</a>
</Trans>
</p>
))}
{visible && (
<p className="ui__ notice">
<span className="sondage__text">
<Trans>Votre avis nous intéresse !</Trans>
</span>
<br />
<Smiley
text=":)"
hoverColor="#16a085"
onClick={this.onSmileyClick}
/>
<Smiley
text=":|"
hoverColor="#f39c12"
onClick={this.onSmileyClick}
/>
</p>
)}
</div>
)
}
}

View File

@ -162,7 +162,8 @@
animation: push-button-left 0.1s ease-out alternate-reverse 2;
}
.ui__.small.button {
.ui__.small.button,
.ui__.small.inverted-button {
font-size: 80%;
line-height: 1rem;
padding: 0.4rem 0.8rem;

View File

@ -120,3 +120,7 @@ strong,
b {
font-weight: 500;
}
textarea {
font-family: inherit;
}

View File

@ -39,7 +39,7 @@ button {
padding: 0 0.6rem;
}
p.ui__.notice {
.ui__.notice {
font-size: 90%;
color: rgba(0, 0, 0, 0.7);
margin-top: 2em;

View File

@ -153,11 +153,21 @@ simulation-end:
text: You have reached the most accurate estimate. You can now turn your hiring project into reality.
cta: Know the procedures
feedback:
question: Was this information useful to you?
bad:
headline: We're sorry we didn't give you complete satisfaction
support: If you wish, you can send us an email at <1>contact@embauche.beta.gouv.fr</1>. We guarantee to answer you as quickly as possible, and to do everything we can to help you.
good:
headline: Thanks for your feedback!
support: If you have a remark, or an idea of improvement, do not hesitate to contact us directly by mail at <1>contact@embauche.beta.gouv.fr</1>
headline: We're sorry we didn't give you complete satisfaction.
question: What was the main problem?
answer:
notUnderstandable: This page lacks clarity
notRelevant: This page has content that does not apply to me
poorContent: I don't find what I'm looking for
other: Other
form:
headline: Your feedback is valuable to us in order to continuously improve this site. What should we work on to better meet your expectations?
email: Your email (if you would like an answer from us)
thanks: Thank you for your feedback!
Janvier 2019: January 2019
d'aides: of aid
Oui: Yes
Non: No
Envoyer: Send

View File

@ -0,0 +1,5 @@
export let feedbackBlacklist = [
'/',
'/company/legal-status',
'/company/legal-status/number-of-associates'
]

View File

@ -1,6 +1,8 @@
.footer-container {
margin-top: 2rem;
}
.footer {
background-color: rgba(41, 117, 209, 0.133);
margin-top: 2rem;
padding-top: 2rem;
padding-bottom: 2rem;
}

View File

@ -1,31 +1,43 @@
import PageFeedback from 'Components/Feedback/PageFeedback'
import withColours from 'Components/utils/withColours'
import urssafSvg from 'Images/urssaf.svg'
import { compose } from 'ramda'
import React from 'react'
import { withRouter } from 'react-router-dom'
import { feedbackBlacklist } from '../../config'
import './Footer.css'
import betaGouvSvg from './logo-betagouv.svg'
const Footer = ({ colours: { colour } }) => (
<footer className="footer" style={{ backgroundColor: `${colour}22` }}>
<div className="ui__ container">
<div id="footerIcons">
<a href="https://urssaf.fr">
<img src={urssafSvg} alt="un service fourni par l'URSSAF" />
</a>
<a href="https://beta.gouv.fr">
<img
src={betaGouvSvg}
alt="un service de lEtat français incubé par beta.gouv.fr"
/>
</a>
const Footer = ({ colours: { colour }, location }) => (
<div className="footer-container">
{!feedbackBlacklist.includes(location.pathname) && (
<PageFeedback key={location.pathname} />
)}
<footer className="footer" style={{ backgroundColor: `${colour}22` }}>
<div className="ui__ container">
<div id="footerIcons">
<a href="https://urssaf.fr">
<img src={urssafSvg} alt="un service fourni par l'URSSAF" />
</a>
<a href="https://beta.gouv.fr">
<img
src={betaGouvSvg}
alt="un service de lEtat français incubé par beta.gouv.fr"
/>
</a>
</div>
<p className="ui__ notice">
This website is provided by the{' '}
<a href="https://www.urssaf.fr">URSSAF</a>, the French social security
contributions collector, and the governments public startup
incubator, <a href="https://beta.gouv.fr">beta.gouv.fr</a>.
</p>
</div>
<p className="ui__ notice">
This website is provided by the{' '}
<a href="https://www.urssaf.fr">URSSAF</a>, the French social security
contributions collector, and the governments public startup incubator,{' '}
<a href="https://beta.gouv.fr">beta.gouv.fr</a>.
</p>
</div>
</footer>
</footer>
</div>
)
export default withColours(Footer)
export default compose(
withRouter,
withColours
)(Footer)