Ajout d'un formulaire de retour à la fin de la conversation

pull/8/head
mama 2017-05-18 16:04:23 +02:00
parent c46e609ed5
commit 51783e64ab
14 changed files with 224 additions and 152 deletions

View File

@ -12,57 +12,57 @@
},
"dependencies": {
"babel-runtime": "^6.23.0",
"classnames": "^2.2.1",
"classnames": "^2.2.5",
"deep-assign": "^2.0.0",
"install": "^0.8.2",
"js-yaml": "^3.7.0",
"install": "^0.10.1",
"js-yaml": "^3.8.4",
"marked": "^0.3.6",
"nearley": "^2.7.14",
"npm": "^4.4.1",
"nearley": "^2.9.2",
"npm": "^4.6.1",
"ramda": "^0.23.0",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-redux": "^5.0.3",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.1",
"reduce-reducers": "^0.1.2",
"redux": "^3.6.0",
"redux-form": "^6.6.3",
"redux-saga": "^0.14.3",
"reselect": "^2.5.2",
"whatwg-fetch": "^2.0.2"
"redux-form": "^6.7.0",
"redux-saga": "^0.15.3",
"reselect": "^3.0.1",
"whatwg-fetch": "^2.0.3"
},
"devDependencies": {
"autoprefixer": "^6.3.3",
"babel": "^6.23.0",
"babel-core": "^6.24.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.4.0",
"babel-plugin-transform-class-properties": "^6.23.0",
"autoprefixer": "^7.1.1",
"babel-cli": "^6.23.0",
"babel-core": "^6.24.1",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.0.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-do-expressions": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.2.1",
"babel-preset-react": "^6.23.0",
"core-js": "^2.2.0",
"css-loader": "^0.27.3",
"eslint": "^3.13.1",
"eslint-plugin-react": "^6.9.0",
"express": "^4.13.3",
"file-loader": "^0.10.1",
"html-loader": "^0.4.2",
"img-loader": "^1.2.2",
"babel-preset-env": "^1.4.0",
"babel-preset-react": "^6.24.1",
"core-js": "^2.4.1",
"css-loader": "^0.28.1",
"eslint": "^3.19.0",
"eslint-plugin-react": "^7.0.1",
"express": "^4.15.3",
"file-loader": "^0.11.1",
"html-loader": "^0.4.5",
"img-loader": "^2.0.0",
"json-loader": "^0.5.4",
"nearley-loader": "0.0.2",
"postcss-loader": "^1.2.2",
"postcss-loader": "^2.0.5",
"react-hot-loader": "^3.0.0-beta.6",
"redux-devtools": "^3.2.0",
"redux-devtools-dock-monitor": "^1.1.1",
"redux-devtools-log-monitor": "^1.0.9",
"style-loader": "^0.13.0",
"url-loader": "^0.5.7",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.4.2",
"redux-devtools": "^3.4.0",
"redux-devtools-dock-monitor": "^1.1.2",
"redux-devtools-log-monitor": "^1.3.0",
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"webpack": "^2.5.1",
"webpack-dev-server": "^2.4.5",
"yaml-loader": "^0.4.0"
},
"scripts": {

View File

@ -13,6 +13,7 @@ rules:
no-global-assign: 0
no-unsafe-negation: 0
no-undef: 1
no-mixed-spaces-and-tabs: 1
react/jsx-uses-vars: 2
react/jsx-uses-react: 2

View File

@ -1,44 +0,0 @@
p {
color: #333;
}
#sim section {
padding: 2em;
}
#sim > h1 {
color: #333350;
margin-top: 3%;
text-align: center;
font-size: 350%;
font-weight: 800;
}
#conversation {
margin: 3em auto;
font-size: 110%;
line-height: normal;
display: flex;
align-items: flex-start;
min-height: 10em;
max-width: 90%;
max-height: 85%;
}
#fin {
margin: 6em auto;
width: 20em;
display: block;
text-align: center;
font-style: italic;
}
#fin p:first-of-type {
font-weight: bold
}
#questions-answers {
min-width: 50%;
transition: width 1s;
}

View File

@ -13,7 +13,7 @@
left: 0;
box-shadow: 1px -7px 20px 2px #ccc;
/*opacity: 0;*/
opacity: 0;
transform: translateY(12em);
transition: transform .5s;
transition-delay: .3s;
@ -21,6 +21,7 @@
}
#results.show {
transform: translateY(0);
opacity: 1;
}

View File

@ -0,0 +1,81 @@
import React, {Component} from 'react'
import HoverDecorator from 'Components/HoverDecorator'
import 'whatwg-fetch'
import {connect} from 'react-redux'
@connect(
state => ({
sessionId: state.sessionId
})
)
export default class Satisfaction extends Component {
state = {
answer: false
}
sendSatisfaction(satisfait) {
fetch('https://embauche.beta.gouv.fr/retour-syso', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fields: {
satisfait,
message: '', //pas de message pour l'instant
date: new Date().toISOString(),
id: this.props.sessionId,
url: document.location.href.toString()
}
})
}).then(response => {
if (!response.ok)
return console.log('Erreur dans la récolte de la satisfaction') //eslint-disable-line no-console
this.setState({answer: satisfait})
})
}
render() {
let {answer} = this.state
if (answer)
return (
<p>
{answer === ':)' ? 'Une suggestion' : 'Un problème'} ? Envie de discuter ? <br/>
<a href={"mailto:contact@embauche.beta.gouv.fr?subject=Suggestion pour le simulateur " + this.props.simu}>
Écrivez-nous <i className="fa fa-envelope-open-o" aria-hidden="true" style={{margin: '0 .3em'}}></i>
</a>
</p>
)
return (
<p id="satisfaction">
Vous êtes satisfait du simulateur ?
{" "}
<Smiley text=":)" hoverColor="#16a085" clicked={s => this.sendSatisfaction(s)}/>
<Smiley text=":|" hoverColor="#f39c12" clicked={s => this.sendSatisfaction(s)}/>
</p>
)
}
}
@HoverDecorator
export class Smiley extends Component {
render() {
return (
<button
onClick={() => this.props.clicked(this.props.text)}
className="satisfaction-smiley"
style={
this.props.hover
? {
background: this.props.hoverColor,
color: "white",
borderColor: "transparent"
}
: {}
}
>
{this.props.text}
</button>
)
}
}

View File

@ -40,7 +40,7 @@
#sim .intro {
font-style: italic;
font-size: 100%;
margin-bottom: 4%;
margin-bottom: 3%;
}
#sim .intro > div {
margin: 1em 0;
@ -90,6 +90,24 @@
}
#conversation {
margin: 3em auto;
font-size: 110%;
line-height: normal;
display: flex;
align-items: flex-start;
min-height: 10em;
max-width: 90%;
max-height: 85%;
}
#questions-answers {
min-width: 50%;
transition: width 1s;
}
#foldedSteps .header {
margin-bottom: 1em;
text-align: center;
@ -106,3 +124,41 @@
margin-right: .3em;
vertical-align: top
}
#fin {
margin: 0 auto;
width: 80%;
display: flex;
align-items: flex-end;
font-style: italic;
}
#fin-text {
width: 50%;
margin-left: 2em;
display: inline-block;
}
#fin p:first-of-type {
font-weight: bold
}
#fin img {
width: 25%;
display: inline-block;
}
.satisfaction-smiley {
color: #4A89DC;
font-size: 90%;
font-weight: bold;
border-radius: 6em;
width: 2em;
text-align: center;
display: inline-block;
border: 2px solid #4A89DC;
margin: 0 .6em;
padding: .3em 0;
transform: rotate(90deg)
}

View File

@ -1,17 +1,17 @@
import React, {Component} from 'react'
import './CDD.css'
import {reduxForm, formValueSelector, reset} from 'redux-form'
import {connect} from 'react-redux'
import './conversation/conversation.css'
import {START_CONVERSATION} from '../actions'
import Aide from './Aide'
import R from 'ramda'
import {Redirect, Link, withRouter} from 'react-router-dom'
import {createMarkdownDiv} from '../engine/marked'
import './Simulateur.css'
import Aide from './Aide'
import {createMarkdownDiv} from 'Engine/marked'
import {findRuleByName, decodeRuleName} from 'Engine/rules'
import 'Components/conversation/conversation.css'
import 'Components/Simulateur.css'
import classNames from 'classnames'
import {findRuleByName, decodeRuleName} from '../engine/rules'
import {capitalise0} from '../utils'
import Satisfaction from 'Components/Satisfaction'
let situationSelector = formValueSelector('conversation')
@ -27,7 +27,7 @@ let situationSelector = formValueSelector('conversation')
}),
dispatch => ({
startConversation: rootVariable => dispatch({type: START_CONVERSATION, rootVariable}),
resetForm: rootVariable => dispatch(reset('conversation'))
resetForm: () => dispatch(reset('conversation'))
})
)
export default class extends React.Component {
@ -39,7 +39,7 @@ export default class extends React.Component {
}
}
} = this.props,
name = decodeRuleName(encodedName)
name = decodeRuleName(encodedName)
this.encodedName = encodedName
this.name = name
@ -58,8 +58,8 @@ export default class extends React.Component {
sim = path =>
R.path(R.unless(R.is(Array), R.of)(path))(this.rule.simulateur || {}),
reinitalise = () => {
this.props.resetForm(this.name);
this.props.startConversation(this.name);
this.props.resetForm(this.name)
this.props.startConversation(this.name)
}
@ -96,7 +96,9 @@ export default class extends React.Component {
Pour simplifier, les résultats sont calculés par mois de contrat, et pour un temps complet.
</p>
<p>
N'hésitez pas à nous écrire <Link to="/contact"><i className="fa fa-envelope-open-o" aria-hidden="true" style={{margin: '0 .3em'}}></i></Link> ! La loi française est très ciblée, et donc complexe. Nous pouvons la rendre plus transparente.
N'hésitez pas à nous écrire <Link to="/contact">
<i className="fa fa-envelope-open-o" aria-hidden="true" style={{margin: '0 .3em'}}></i>
</Link> ! La loi française est très ciblée, et donc complexe. Nous pouvons la rendre plus transparente.
</p>
</div>
</div>
@ -136,7 +138,7 @@ export default class extends React.Component {
}}
</div>
{unfoldedSteps.length == 0 &&
<Conclusion />}
<Conclusion simu={this.name}/>}
</div>
<Aide />
</div>
@ -153,15 +155,15 @@ class Conclusion extends Component {
return (
<div id="fin">
<img src={require('../images/fin.png')} />
<p>
Nous n'avons plus de questions : votre simulation est terminée.
</p>
<p>
Cliquez sur les obligations en bas pour comprendre vos résultats.
</p>
<p>
Une remarque ? <Link to="/contact">Écrivez-nous !</Link>
</p>
<div id="fin-text">
<p>
Votre simulation est terminée !
</p>
<p>
N'hésitez pas à modifier vos réponses, ou cliquez sur vos résultats pour comprendre le calcul.
</p>
<Satisfaction simu={this.props.simu}/>
</div>
</div>
)
}

View File

@ -2,14 +2,14 @@ import React, { Component } from 'react'
import './Layout.css'
import './reset.css'
import {Link, Route, BrowserRouter as Router, Switch} from 'react-router-dom'
import HomeEmbauche from '../components/HomeEmbauche'
import HomeSyso from '../components/HomeSyso'
import Rule from '../components/rule/Rule'
import Route404 from '../components/Route404'
import Contact from '../components/Contact'
import Simulateur from '../components/Simulateur'
import Results from '../components/Results'
import R from 'ramda'
import HomeEmbauche from 'Components/HomeEmbauche'
import HomeSyso from 'Components/HomeSyso'
import Rule from 'Components/rule/Rule'
import Route404 from 'Components/Route404'
import Contact from 'Components/Contact'
import Simulateur from 'Components/Simulateur'
import Results from 'Components/Results'
export default class Layout extends Component {
render() {

View File

@ -1,9 +1,9 @@
import React from 'react'
import Explicable from '../components/conversation/Explicable'
import Explicable from 'Components/conversation/Explicable'
import R from 'ramda'
import Question from '../components/conversation/Question'
import Input from '../components/conversation/Input'
import formValueTypes from '../components/conversation/formValueTypes'
import Question from 'Components/conversation/Question'
import Input from 'Components/conversation/Input'
import formValueTypes from 'Components/conversation/formValueTypes'
import {analyseSituation} from './traverse'
import {formValueSelector} from 'redux-form'
import { STEP_ACTION, START_CONVERSATION} from '../actions'

View File

@ -51,7 +51,7 @@ export let disambiguateRuleReference = ({ns, name}, partialName) => {
fragments = ns.split(' . '), // ex. [CDD . événements . rupture]
pathPossibilities = // -> [ [CDD . événements . rupture], [CDD . événements], [CDD] ]
R.range(0, fragments.length + 1)
.map(nbEl => R.take(nbEl)(fragments))
.map(nbEl => R.take(nbEl)(fragments))
.reverse(),
found = R.reduce((res, path) =>
R.when(

View File

@ -1,26 +1,17 @@
import React from 'react'
import { render } from 'react-dom'
import { compose, createStore, applyMiddleware } from 'redux'
import { compose, createStore } from 'redux'
import App from './containers/App'
import reducers from './reducers'
import DevTools from './DevTools'
import { AppContainer } from 'react-hot-loader'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const createFinalStore = compose(
// Enables your middleware:
applyMiddleware(sagaMiddleware), // any Redux middleware, e.g. redux-thunk
// Provides support for DevTools:
DevTools.instrument()
)(createStore)
const store = createFinalStore(reducers)
sagaMiddleware.run(rootSaga)
let store = createStore(
reducers,
compose(
DevTools.instrument()
)
)
let anchor = document.querySelector('#js')
@ -29,7 +20,7 @@ render(
anchor
)
// Hot react component reloading. Unstable but helpful.
// Hot react component reloading. Unstable but helpful.
if (module.hot) {
module.hot.accept('./containers/App', () => {
// If you use Webpack 2 in ES modules mode, you can

View File

@ -40,6 +40,7 @@ function pointedOutObjectives(state=[], {type, objectives}) {
export default reduceReducers(
combineReducers({
sessionId: (id = Math.floor(Math.random() * 1000000000000) + '') => id,
// this is handled by redux-form, pas touche !
form: formReducer,

View File

@ -1,16 +0,0 @@
import { takeEvery} from 'redux-saga/effects'
// import { call, put} from 'redux-saga/effects'
// import Promise from 'core-js/fn/promise'
// Nothing happening here !
function* handleSubmitStep() {
}
function* watchSteps() {
yield* takeEvery('SUBMIT_STEP', handleSubmitStep)
}
export default function* rootSaga() {
yield [ watchSteps() ]
}

View File

@ -7,6 +7,7 @@ module.exports = {
devtool: 'cheap-module-source-map',
entry: prodEnv ? [
'babel-polyfill',
'whatwg-fetch',
'./source/entry.js'
] : [
'webpack-dev-server/client?http://localhost:3000/',
@ -23,7 +24,8 @@ module.exports = {
resolve: {
alias: {
Engine: path.resolve('source/engine/'),
Règles: path.resolve('règles/')
Règles: path.resolve('règles/'),
Components: path.resolve('source/components/')
}
},
module: {
@ -70,10 +72,7 @@ module.exports = {
},
plugins: [
new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ProvidePlugin({
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch',
}),
new webpack.NoEmitOnErrorsPlugin()
]
.concat(!prodEnv ? [new webpack.HotModuleReplacementPlugin()] : [])
.concat(prodEnv ? [new webpack.optimize.UglifyJsPlugin()] : []),