💚 fix flow

Suppression des décorateurs.

Problème :
Les décorateurs que l'on utilisait correspondait à une ancienne
version de la proposal tc39, encore en stage 1 (voir 0). La
proposition a complètement évolué, pour ne plus du tout avoir
la même forme que précédement.

Au lieu de garder la version 'legacy', on choisit de se séparer
des décorateur, étant donné que le nouveau use case n'a plus rien
à voir, et que l'ancienne version peut être gérée de manière
quasi équivalente avec des fonctions et des compose
pull/418/head
Johan Girod 2018-11-14 16:51:37 +01:00
parent 05d1c20aa0
commit 1d69feafd6
64 changed files with 2269 additions and 2154 deletions

View File

@ -34,7 +34,7 @@
"react-helmet": "^5.2.0",
"react-highlight-words": "^0.11.0",
"react-hot-loader": "^4.3.11",
"react-i18next": "^7.13.0",
"react-i18next": "^8.3.0",
"react-redux": "^5.0.7",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
@ -48,7 +48,7 @@
"redux": "^3.7.2",
"redux-form": "^7.4.2",
"redux-thunk": "^2.3.0",
"reselect": "^3.0.1",
"reselect": "^4.0.0",
"screenfull": "^3.3.2"
},
"scripts": {
@ -71,10 +71,9 @@
},
"devDependencies": {
"@babel/core": "^7.1.0",
"@babel/plugin-proposal-decorators": "^7.1.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-syntax-decorators": "^7.1.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-flow": "^7.0.0-beta.51",
@ -84,8 +83,6 @@
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators": "^6.24.1",
"babel-plugin-transform-do-expressions": "^6.22.0",
"babel-plugin-webpack-alias": "^2.1.2",
"chai": "^4.1.2",

View File

@ -13,11 +13,10 @@
"@babel/flow"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
"transform-do-expressions",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-object-rest-spread",
"transform-class-properties",
"transform-do-expressions",
"syntax-dynamic-import",
"react-hot-loader/babel",
["webpack-alias", { "config": "./source/webpack.dev.js" }]

View File

@ -2,55 +2,60 @@ import { EXPLAIN_VARIABLE } from 'Actions/actions'
import withColours from 'Components/utils/withColours'
import marked from 'Engine/marked'
import { findRuleByDottedName } from 'Engine/rules'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { flatRulesSelector } from 'Selectors/analyseSelectors'
import './Aide.css'
import References from './rule/References'
@connect(
state => ({
explained: state.explainedVariable,
flatRules: flatRulesSelector(state)
}),
dispatch => ({
stopExplaining: () => dispatch({ type: EXPLAIN_VARIABLE })
})
export default compose(
connect(
state => ({
explained: state.explainedVariable,
flatRules: flatRulesSelector(state)
}),
dispatch => ({
stopExplaining: () => dispatch({ type: EXPLAIN_VARIABLE })
})
),
withColours
)(
class Aide extends Component {
renderExplanationMarkdown(explanation, term) {
return marked(`### ${term} \n\n${explanation}`)
}
render() {
let { flatRules, explained, stopExplaining, colours } = this.props
if (!explained) return <section id="helpWrapper" />
let rule = findRuleByDottedName(flatRules, explained),
text = rule.description,
refs = rule.références
return (
<div id="helpWrapper" className="active">
<section id="help">
<i
className="fa fa-times-circle"
onClick={stopExplaining}
style={{ color: colours.colour }}
/>
<p
dangerouslySetInnerHTML={{
__html: this.renderExplanationMarkdown(text, rule.title)
}}
/>
{refs && (
<div>
<p>Pour en savoir plus: </p>
<References refs={refs} />
</div>
)}
</section>
</div>
)
}
}
)
@withColours
export default class Aide extends Component {
renderExplanationMarkdown(explanation, term) {
return marked(`### ${term} \n\n${explanation}`)
}
render() {
let { flatRules, explained, stopExplaining, colours } = this.props
if (!explained) return <section id="helpWrapper" />
let rule = findRuleByDottedName(flatRules, explained),
text = rule.description,
refs = rule.références
return (
<div id="helpWrapper" className="active">
<section id="help">
<i
className="fa fa-times-circle"
onClick={stopExplaining}
style={{ color: colours.colour }}
/>
<p
dangerouslySetInnerHTML={{
__html: this.renderExplanationMarkdown(text, rule.title)
}}
/>
{refs && (
<div>
<p>Pour en savoir plus: </p>
<References refs={refs} />
</div>
)}
</section>
</div>
)
}
}

View File

@ -1,34 +1,32 @@
import React, { Component } from 'react'
import withLanguage from 'Components/utils/withLanguage'
import './AnimatedTargetValue.css'
import React, { Component } from 'react'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import './AnimatedTargetValue.css'
@withLanguage
class AnimatedTargetValue extends Component {
render() {
let { value, language } = this.props
let formattedValue =
value == null
? ''
: Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR',
maximumFractionDigits: 0,
minimumFractionDigits: 0
}).format(value)
return (
<ReactCSSTransitionGroup
transitionName="flash"
transitionEnterTimeout={100}
transitionLeaveTimeout={100}>
<span key={value} className="Rule-value">
{' '}
<span>{formattedValue}</span>
</span>
</ReactCSSTransitionGroup>
)
export default withLanguage(
class AnimatedTargetValue extends Component {
render() {
let { value, language } = this.props
let formattedValue =
value == null
? ''
: Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR',
maximumFractionDigits: 0,
minimumFractionDigits: 0
}).format(value)
return (
<ReactCSSTransitionGroup
transitionName="flash"
transitionEnterTimeout={100}
transitionLeaveTimeout={100}>
<span key={value} className="Rule-value">
{' '}
<span>{formattedValue}</span>
</span>
</ReactCSSTransitionGroup>
)
}
}
}
export default AnimatedTargetValue
)

View File

@ -2,7 +2,7 @@
import withTracker from 'Components/utils/withTracker'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import safeLocalStorage from '../../storage/safeLocalStorage'
@ -93,37 +93,36 @@ class PageFeedback extends Component<Props, State> {
return (
!this.props.blacklist.includes(this.props.location.pathname) && (
<div className="feedback-page ui__ container notice">
{!this.state.showForm &&
!this.state.showThanks && (
<>
<div style={{ flex: 1 }}>
{this.props.customMessage || (
<Trans i18nKey="feedback.question">
Cette page vous a-t-elle été utile ?
</Trans>
)}{' '}
<button
style={{ marginLeft: '0.4rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: true })}>
<Trans>Oui</Trans>
</button>{' '}
<button
style={{ marginLeft: '0.4rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: false })}>
<Trans>Non</Trans>
</button>
</div>
<button
className="ui__ link-button"
onClick={this.handleErrorReporting}>
<Trans i18nKey="feedback.reportError">
Signaler une erreur
{!this.state.showForm && !this.state.showThanks && (
<>
<div style={{ flex: 1 }}>
{this.props.customMessage || (
<Trans i18nKey="feedback.question">
Cette page vous a-t-elle été utile ?
</Trans>
)}{' '}
<button
style={{ marginLeft: '0.4rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: true })}>
<Trans>Oui</Trans>
</button>{' '}
</>
)}
<button
style={{ marginLeft: '0.4rem' }}
className="ui__ link-button"
onClick={() => this.handleFeedback({ useful: false })}>
<Trans>Non</Trans>
</button>
</div>
<button
className="ui__ link-button"
onClick={this.handleErrorReporting}>
<Trans i18nKey="feedback.reportError">
Signaler une erreur
</Trans>
</button>{' '}
</>
)}
{this.state.showThanks && (
<div>
<Trans i18nKey="feedback.thanks">
@ -150,7 +149,7 @@ const PageFeedbackWithRouter = ({ location, ...props }) => (
)
export default compose(
translate(),
withI18n(),
withTracker,
withRouter
)(PageFeedbackWithRouter)

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import { compose } from 'ramda'
import React, { Component } from 'react'
import emoji from 'react-easy-emoji'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
@ -10,37 +10,38 @@ const languageCodeToEmoji = {
fr: '🇫🇷'
}
@withRouter
@translate()
@connect(
null,
dispatch => ({
changeLanguage: lang => dispatch({ type: 'SWITCH_LANG', lang })
})
)
export default class LangSwitcher extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
getUnusedLanguageCode = () => {
let languageCode = this.context.i18n.language
return !languageCode || languageCode === 'fr' ? 'en' : 'fr'
}
export default compose(
withRouter,
withI18n(),
connect(
null,
dispatch => ({
changeLanguage: lang => dispatch({ type: 'SWITCH_LANG', lang })
})
)
)(
class LangSwitcher extends Component {
getUnusedLanguageCode = () => {
let languageCode = this.props.i18n.language
return !languageCode || languageCode === 'fr' ? 'en' : 'fr'
}
changeLanguage = () => {
let nextLanguage = this.getUnusedLanguageCode()
this.props.changeLanguage(nextLanguage)
this.context.i18n.changeLanguage(nextLanguage)
this.forceUpdate()
changeLanguage = () => {
let nextLanguage = this.getUnusedLanguageCode()
this.props.changeLanguage(nextLanguage)
this.props.i18n.changeLanguage(nextLanguage)
this.forceUpdate()
}
render() {
const languageCode = this.getUnusedLanguageCode()
return (
<button
className={this.props.className || 'ui__ link-button'}
onClick={this.changeLanguage}>
{emoji(languageCodeToEmoji[languageCode])}{' '}
{languageCode.toUpperCase()}
</button>
)
}
}
render() {
const languageCode = this.getUnusedLanguageCode()
return (
<button
className={this.props.className || 'ui__ link-button'}
onClick={this.changeLanguage}>
{emoji(languageCodeToEmoji[languageCode])} {languageCode.toUpperCase()}
</button>
)
}
}
)

View File

@ -1,10 +1,9 @@
import Overlay from 'Components/Overlay'
import { ScrollToTop } from 'Components/utils/Scroll'
import React, { Component } from 'react'
import { Trans } from 'react-i18next'
import translate from 'react-i18next/dist/commonjs/translate'
import { Trans, withI18n } from 'react-i18next'
export const LegalNoticeContent = translate()(() => (
export const LegalNoticeContent = withI18n()(() => (
<>
<h1>
<Trans i18nKey="legalNotice.title">Mentions légales</Trans>

View File

@ -5,7 +5,7 @@ import {
} from 'Actions/actions'
import { compose } from 'ramda'
import React from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { noUserInputSelector } from 'Selectors/analyseSelectors'
import { LinkButton } from 'Ui/Button'
@ -33,7 +33,8 @@ const PreviousSimulationBanner = ({
<Trans i18nKey="previousSimulationBanner.retrieveButton">
Retrouver ma simulation
</Trans>
</LinkButton>.{' '}
</LinkButton>
.{' '}
<LinkButton onClick={deletePreviousSimulation}>
<Trans>Effacer</Trans>
</LinkButton>
@ -41,7 +42,7 @@ const PreviousSimulationBanner = ({
)
export default compose(
translate(),
withI18n(),
connect(
state => ({
previousSimulation: state.previousSimulation,

View File

@ -4,27 +4,28 @@ import { connect } from 'react-redux'
import { nextStepsSelector } from 'Selectors/analyseSelectors'
import './ProgressTip.css'
@connect(state => ({
export default connect(state => ({
nextSteps: nextStepsSelector(state)
}))
export default class ProgressTip extends Component {
render() {
let { nextSteps } = this.props,
nbQuestions = nextSteps.length
if (nbQuestions === 0) return null
}))(
class ProgressTip extends Component {
render() {
let { nextSteps } = this.props,
nbQuestions = nextSteps.length
if (nbQuestions === 0) return null
return (
<div className="progressTip">
<p>
{nbQuestions === 1 ? (
<Trans i18nKey="lastQ">dernière question !</Trans>
) : (
<Trans i18nKey="questionsLeft" count={nbQuestions}>
moins de {{ nbQuestions }} questions
</Trans>
)}
</p>
</div>
)
return (
<div className="progressTip">
<p>
{nbQuestions === 1 ? (
<Trans i18nKey="lastQ">dernière question !</Trans>
) : (
<Trans i18nKey="questionsLeft" count={nbQuestions}>
moins de {{ nbQuestions }} questions
</Trans>
)}
</p>
</div>
)
}
}
}
)

View File

@ -8,7 +8,7 @@ import {
} from 'Engine/rules.js'
import { compose, head, path } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
import { Link, Redirect } from 'react-router-dom'
@ -20,80 +20,88 @@ import Namespace from './rule/Namespace'
import Rule from './rule/Rule'
import './RulePage.css'
import SearchButton from './SearchButton'
@connect(state => ({
themeColours: state.themeColours,
valuesToShow: !noUserInputSelector(state),
flatRules: flatRulesSelector(state)
}))
@translate()
class RulePage extends Component {
render() {
let { flatRules } = this.props,
name = path(['match', 'params', 'name'], this.props),
decodedRuleName = decodeRuleName(name)
if (decodedRuleName.includes(' . ')) {
if (!findRuleByDottedName(flatRules, decodedRuleName))
return <Redirect to="/404" />
export default compose(
connect(state => ({
themeColours: state.themeColours,
valuesToShow: !noUserInputSelector(state),
flatRules: flatRulesSelector(state)
})),
withI18n()
)(
class RulePage extends Component {
render() {
let { flatRules } = this.props,
name = path(['match', 'params', 'name'], this.props),
decodedRuleName = decodeRuleName(name)
return this.renderRule(decodedRuleName)
if (decodedRuleName.includes(' . ')) {
if (!findRuleByDottedName(flatRules, decodedRuleName))
return <Redirect to="/404" />
return this.renderRule(decodedRuleName)
}
let rules = findRulesByName(flatRules, decodedRuleName)
if (!rules.length) return <Redirect to="/404" />
if (rules.length > 1)
return <DisambiguateRuleQuery rules={rules} flatRules={flatRules} />
let dottedName = head(rules).dottedName
return this.renderRule(dottedName)
}
let rules = findRulesByName(flatRules, decodedRuleName)
if (!rules.length) return <Redirect to="/404" />
if (rules.length > 1)
return <DisambiguateRuleQuery rules={rules} flatRules={flatRules} />
let dottedName = head(rules).dottedName
return this.renderRule(dottedName)
}
renderRule(dottedName) {
return (
<div id="RulePage">
<ScrollToTop />
<div className="rule-page__header ui__ container">
<BackToSimulation
visible={this.props.valuesToShow}
colour={this.props.themeColours.colour}
/>
<SearchButton
className="rule-page__search"
rulePageBasePath="../règle"
/>
renderRule(dottedName) {
return (
<div id="RulePage">
<ScrollToTop />
<div className="rule-page__header ui__ container">
<BackToSimulation
visible={this.props.valuesToShow}
colour={this.props.themeColours.colour}
/>
<SearchButton
className="rule-page__search"
rulePageBasePath="../règle"
/>
</div>
<Rule dottedName={dottedName} />
</div>
<Rule dottedName={dottedName} />
</div>
)
)
}
}
}
@connect(
null,
dispatch => ({
setExample: compose(
dispatch,
setExample
)
})
)
@withRouter
@translate() // Triggers rerender when the language changes
class BackToSimulation extends Component {
render() {
let { colour, setExample, visible } = this.props
return (
<Link
id="toSimulation"
to="../simulation"
onClick={() => {
setExample(null)
}}
style={{ color: colour, visibility: visible ? 'visible' : 'hidden' }}>
<i className="fa fa-arrow-left" aria-hidden="true" />
<Trans i18nKey="back">Reprendre la simulation</Trans>
</Link>
)
const BackToSimulation = compose(
connect(
null,
dispatch => ({
setExample: compose(
dispatch,
setExample
)
})
),
withRouter,
withI18n()
)(
// Triggers rerender when the language changes
class BackToSimulation extends Component {
render() {
let { colour, setExample, visible } = this.props
return (
<Link
id="toSimulation"
to="../simulation"
onClick={() => {
setExample(null)
}}
style={{ color: colour, visibility: visible ? 'visible' : 'hidden' }}>
<i className="fa fa-arrow-left" aria-hidden="true" />
<Trans i18nKey="back">Reprendre la simulation</Trans>
</Link>
)
}
}
}
)
let DisambiguateRuleQuery = ({ rules, flatRules }) => (
<div className="centeredMessage">
@ -112,5 +120,3 @@ let DisambiguateRuleQuery = ({ rules, flatRules }) => (
</ul>
</div>
)
export default RulePage

View File

@ -4,14 +4,13 @@ import PropTypes from 'prop-types'
import { pick } from 'ramda'
import React from 'react'
import Highlighter from 'react-highlight-words'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { Link, Redirect } from 'react-router-dom'
import Select from 'react-select'
import 'react-select/dist/react-select.css'
import { capitalise0 } from '../utils'
@translate()
export default class SearchBar extends React.Component {
class SearchBar extends React.Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
@ -99,24 +98,25 @@ export default class SearchBar extends React.Component {
this.inputElement = el
}}
/>
{this.props.showDefaultList &&
!this.state.inputValue && (
<ul>
{rules.map(rule => (
<li key={rule.dottedName}>
<Link
to={
this.props.rulePagesBasePath +
'/' +
encodeRuleName(rule.name)
}>
{rule.title || capitalise0(rule.name)}
</Link>
</li>
))}
</ul>
)}
{this.props.showDefaultList && !this.state.inputValue && (
<ul>
{rules.map(rule => (
<li key={rule.dottedName}>
<Link
to={
this.props.rulePagesBasePath +
'/' +
encodeRuleName(rule.name)
}>
{rule.title || capitalise0(rule.name)}
</Link>
</li>
))}
</ul>
)}
</>
)
}
}
export default withI18n()(SearchBar)

View File

@ -1,64 +1,68 @@
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { flatRulesSelector } from 'Selectors/analyseSelectors'
import { LinkButton } from 'Ui/Button'
import Overlay from './Overlay'
import SearchBar from './SearchBar'
@connect(state => ({
flatRules: flatRulesSelector(state)
}))
@translate()
export default class SearchButton extends Component {
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
export default compose(
connect(state => ({
flatRules: flatRulesSelector(state)
})),
withI18n()
)(
class SearchButton extends Component {
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
window.addEventListener('keydown', this.boundHandleKeyDown)
window.addEventListener('keydown', this.boundHandleKeyDown)
}
handleKeyDown(e) {
if (!(e.ctrlKey && e.key === 'k')) return
this.setState({ visible: true })
e.preventDefault()
e.stopPropagation()
return false
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
state = {
visible: false
}
close = () => this.setState({ visible: false })
render() {
let { flatRules } = this.props
return this.state.visible ? (
<Overlay onClose={this.close}>
<h2>
<Trans>Chercher une règle</Trans>
</h2>
<SearchBar
showDefaultList={false}
finally={this.close}
rules={flatRules}
rulePagesBasePath={this.props.rulePagesBasePath}
/>
</Overlay>
) : (
<LinkButton
onClick={() => this.setState({ visible: true })}
className={this.props.className}
style={this.props.style}>
<i
className="fa fa-search"
aria-hidden="true"
style={{ marginRight: '0.4em' }}
/>
<span>
<Trans>Rechercher</Trans>
</span>
</LinkButton>
)
}
}
handleKeyDown(e) {
if (!(e.ctrlKey && e.key === 'k')) return
this.setState({ visible: true })
e.preventDefault()
e.stopPropagation()
return false
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
state = {
visible: false
}
close = () => this.setState({ visible: false })
render() {
let { flatRules } = this.props
return this.state.visible ? (
<Overlay onClose={this.close}>
<h2>
<Trans>Chercher une règle</Trans>
</h2>
<SearchBar
showDefaultList={false}
finally={this.close}
rules={flatRules}
rulePagesBasePath={this.props.rulePagesBasePath}
/>
</Overlay>
) : (
<LinkButton
onClick={() => this.setState({ visible: true })}
className={this.props.className}
style={this.props.style}>
<i
className="fa fa-search"
aria-hidden="true"
style={{ marginRight: '0.4em' }}
/>
<span>
<Trans>Rechercher</Trans>
</span>
</LinkButton>
)
}
}
)

View File

@ -3,8 +3,9 @@ import AnswerList from 'Components/AnswerList'
import { ScrollToTop } from 'Components/utils/Scroll'
import withColours from 'Components/utils/withColours'
import withLanguage from 'Components/utils/withLanguage'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Redirect, withRouter } from 'react-router'
import { Link } from 'react-router-dom'
@ -26,151 +27,153 @@ import ResultView from './ResultView'
import './Simu.css'
import TargetSelection from './TargetSelection'
@withRouter
@withColours
@translate() // Triggers rerender when the language changes
@connect(
state => ({
blockingInputControls: blockingInputControlsSelector(state),
conversationStarted: state.conversationStarted,
validInputEntered: validInputEnteredSelector(state),
arePreviousAnswers: state.conversationSteps.foldedSteps.length !== 0,
nextSteps: state.conversationStarted && nextStepsSelector(state),
userInput: noUserInputSelector(state)
}),
{
startConversation
}
)
@withLanguage
class Simulation extends Component {
state = {
displayPreviousAnswers: false
}
render() {
let {
colours,
conversationStarted,
arePreviousAnswers,
nextSteps,
startConversation,
blockingInputControls,
match,
validInputEntered,
location
} = this.props
const displayConversation = conversationStarted && !blockingInputControls
const simulationCompleted =
!blockingInputControls && conversationStarted && !nextSteps.length
const displayPreviousAnswers =
arePreviousAnswers && this.state.displayPreviousAnswers
const simulationHomePath = normalizeBasePath(match.path).replace(
/\/simulation\/$/,
''
)
return (
<>
<div id="simu">
<QuickLink />
{location.pathname.endsWith('/simulation') && (
<>
{!conversationStarted && <Redirect to={simulationHomePath} />}
<Link to={simulationHomePath} style={{ position: 'absolute' }}>
<i
className="fa fa-arrow-left"
aria-hidden="true"
style={{ marginRight: '0.5rem' }}
/>
<Trans>Retour</Trans>
</Link>
export default compose(
withRouter,
withColours,
withI18n(), // Triggers rerender when the language changes
connect(
state => ({
blockingInputControls: blockingInputControlsSelector(state),
conversationStarted: state.conversationStarted,
validInputEntered: validInputEnteredSelector(state),
arePreviousAnswers: state.conversationSteps.foldedSteps.length !== 0,
nextSteps: state.conversationStarted && nextStepsSelector(state),
userInput: noUserInputSelector(state)
}),
{
startConversation
}
),
withLanguage
)(
class Simulation extends Component {
state = {
displayPreviousAnswers: false
}
render() {
let {
colours,
conversationStarted,
arePreviousAnswers,
nextSteps,
startConversation,
blockingInputControls,
match,
validInputEntered,
location
} = this.props
const displayConversation = conversationStarted && !blockingInputControls
const simulationCompleted =
!blockingInputControls && conversationStarted && !nextSteps.length
const displayPreviousAnswers =
arePreviousAnswers && this.state.displayPreviousAnswers
const simulationHomePath = normalizeBasePath(match.path).replace(
/\/simulation\/$/,
''
)
return (
<>
<div id="simu">
<QuickLink />
{location.pathname.endsWith('/simulation') && (
<>
{!conversationStarted && <Redirect to={simulationHomePath} />}
<Link to={simulationHomePath} style={{ position: 'absolute' }}>
<i
className="fa fa-arrow-left"
aria-hidden="true"
style={{ marginRight: '0.5rem' }}
/>
<Trans>Retour</Trans>
</Link>
<div
className="change-answer-link"
style={{
visibility: arePreviousAnswers ? 'visible' : 'hidden'
}}>
<button
className="ui__ link-button"
onClick={() =>
this.setState({ displayPreviousAnswers: true })
}>
<Trans>Modifier mes réponses</Trans>
</button>
</div>
{displayPreviousAnswers && (
<AnswerList
onClose={() =>
this.setState({ displayPreviousAnswers: false })
}
/>
)}
{simulationCompleted && (
<>
<h1>
<Trans i18nKey="simulation-end.title">
Plus de questions !
</Trans>
</h1>
<p>
<Trans i18nKey="simulation-end.text">
Vous avez atteint l'estimation la plus précise. Vous
pouvez maintenant concrétiser votre projet d'embauche.
</Trans>
</p>
{this.props.displayHiringProcedures && (
<div style={{ textAlign: 'center' }}>
<Link
className="ui__ button"
to={sitePaths().démarcheEmbauche}>
<Trans i18nKey="simulation-end.cta">
Connaître les démarches
</Trans>
</Link>
</div>
)}
<br />
</>
)}
{displayConversation && (
<>
<ScrollToTop />
<Conversation textColourOnWhite={colours.textColourOnWhite} />
</>
)}
</>
)}
<TargetSelection colours={colours} />
{location.pathname.endsWith('/simulation') && (
<>
{conversationStarted && (
<Animate.fromBottom>
<ResultView />
<div style={{ textAlign: 'center' }} />
</Animate.fromBottom>
)}
</>
)}
{validInputEntered && (
<PageFeedback
customMessage={
<Trans i18nKey="feedback.simulator">
Ce simulateur vous a plu ?
</Trans>
}
customEventName="rate simulator"
/>
)}
</div>
{!location.pathname.endsWith('/simulation') &&
validInputEntered && (
<div
className="change-answer-link"
style={{
visibility: arePreviousAnswers ? 'visible' : 'hidden'
}}>
<button
className="ui__ link-button"
onClick={() =>
this.setState({ displayPreviousAnswers: true })
}>
<Trans>Modifier mes réponses</Trans>
</button>
</div>
{displayPreviousAnswers && (
<AnswerList
onClose={() =>
this.setState({ displayPreviousAnswers: false })
}
/>
)}
{simulationCompleted && (
<>
<h1>
<Trans i18nKey="simulation-end.title">
Plus de questions !
</Trans>
</h1>
<p>
<Trans i18nKey="simulation-end.text">
Vous avez atteint l'estimation la plus précise. Vous
pouvez maintenant concrétiser votre projet d'embauche.
</Trans>
</p>
{this.props.displayHiringProcedures && (
<div style={{ textAlign: 'center' }}>
<Link
className="ui__ button"
to={sitePaths().démarcheEmbauche}>
<Trans i18nKey="simulation-end.cta">
Connaître les démarches
</Trans>
</Link>
</div>
)}
<br />
</>
)}
{displayConversation && (
<>
<ScrollToTop />
<Conversation
textColourOnWhite={colours.textColourOnWhite}
/>
</>
)}
</>
)}
<TargetSelection colours={colours} />
{location.pathname.endsWith('/simulation') && (
<>
{conversationStarted && (
<Animate.fromBottom>
<ResultView />
<div style={{ textAlign: 'center' }} />
</Animate.fromBottom>
)}
</>
)}
{validInputEntered && (
<PageFeedback
customMessage={
<Trans i18nKey="feedback.simulator">
Ce simulateur vous a plu ?
</Trans>
}
customEventName="rate simulator"
/>
)}
</div>
{!location.pathname.endsWith('/simulation') && validInputEntered && (
<Animate.fromBottom>
<div style={{ textAlign: 'center' }}>
{arePreviousAnswers &&
conversationStarted && (
<button className="ui__ button" onClick={startConversation}>
<Trans>Continuer la simulation</Trans>
</button>
)}
{arePreviousAnswers && conversationStarted && (
<button className="ui__ button" onClick={startConversation}>
<Trans>Continuer la simulation</Trans>
</button>
)}
</div>
<h2>
<Trans>A quoi servent mes cotisations ?</Trans>
@ -204,8 +207,8 @@ class Simulation extends Component {
<PaySlip />
</Animate.fromBottom>
)}
</>
)
</>
)
}
}
}
export default Simulation
)

View File

@ -3,7 +3,7 @@ import InputSuggestions from 'Components/conversation/InputSuggestions'
import withColours from 'Components/utils/withColours'
import withLanguage from 'Components/utils/withLanguage'
import { encodeRuleName, findRuleByDottedName } from 'Engine/rules'
import { propEq } from 'ramda'
import { compose, propEq } from 'ramda'
import React, { Component } from 'react'
import emoji from 'react-easy-emoji'
import { Trans, translate } from 'react-i18next'
@ -37,107 +37,109 @@ export let popularTargetNames = [
'contrat salarié . rémunération . net imposable'
]
@translate()
@reduxForm({
form: 'conversation',
destroyOnUnmount: false
})
@withRouter
@connect(
state => ({
getTargetValue: dottedName =>
formValueSelector('conversation')(state, dottedName),
analysis: analysisWithDefaultsSelector(state),
blockingInputControls: blockingInputControlsSelector(state),
flatRules: flatRulesSelector(state),
noUserInput: noUserInputSelector(state),
conversationStarted: state.conversationStarted,
activeInput: state.activeTargetInput
const TargetSelection = compose(
translate(),
reduxForm({
form: 'conversation',
destroyOnUnmount: false
}),
dispatch => ({
setFormValue: (field, name) =>
dispatch(change('conversation', field, name)),
setActiveInput: name => dispatch({ type: 'SET_ACTIVE_TARGET_INPUT', name })
})
)
class TargetSelection extends Component {
render() {
let {
colours,
noUserInput,
blockingInputControls,
analysis: { controls }
} = this.props
return (
<div id="targetSelection">
{noUserInput && (
<p className="blockingControl">
<Trans i18nKey="enterSalary">
Entrez un salaire <b>mensuel</b>
</Trans>
</p>
)}
<Controls {...{ blockingInputControls, controls }} />
<section
id="targetsContainer"
style={{
color: colours.textColour,
background: `linear-gradient(
withRouter,
connect(
state => ({
getTargetValue: dottedName =>
formValueSelector('conversation')(state, dottedName),
analysis: analysisWithDefaultsSelector(state),
blockingInputControls: blockingInputControlsSelector(state),
flatRules: flatRulesSelector(state),
noUserInput: noUserInputSelector(state),
conversationStarted: state.conversationStarted,
activeInput: state.activeTargetInput
}),
dispatch => ({
setFormValue: (field, name) =>
dispatch(change('conversation', field, name)),
setActiveInput: name =>
dispatch({ type: 'SET_ACTIVE_TARGET_INPUT', name })
})
)
)(
class TargetSelection extends Component {
render() {
let {
colours,
noUserInput,
blockingInputControls,
analysis: { controls }
} = this.props
return (
<div id="targetSelection">
{noUserInput && (
<p className="blockingControl">
<Trans i18nKey="enterSalary">
Entrez un salaire <b>mensuel</b>
</Trans>
</p>
)}
<Controls {...{ blockingInputControls, controls }} />
<section
id="targetsContainer"
style={{
color: colours.textColour,
background: `linear-gradient(
60deg,
${colours.darkColour} 0%,
${colours.colour} 100%
)`
}}>
{this.renderOutputList()}
</section>
</div>
)
}
}}>
{this.renderOutputList()}
</section>
</div>
)
}
renderOutputList() {
let displayedTargets = mainTargetNames.map(target =>
findRuleByDottedName(this.props.flatRules, target)
),
{
conversationStarted,
activeInput,
setActiveInput,
analysis,
noUserInput,
blockingInputControls,
match
} = this.props,
targets = analysis ? analysis.targets : []
renderOutputList() {
let displayedTargets = mainTargetNames.map(target =>
findRuleByDottedName(this.props.flatRules, target)
),
{
conversationStarted,
activeInput,
setActiveInput,
analysis,
noUserInput,
blockingInputControls,
match
} = this.props,
targets = analysis ? analysis.targets : []
return (
<div>
<ul id="targets">
{displayedTargets.map(target => (
<li key={target.name}>
<div className="main">
<Header
{...{
match,
target,
conversationStarted,
isActiveInput: activeInput === target.dottedName,
blockingInputControls
}}
/>
<TargetInputOrValue
{...{
target,
targets,
activeInput,
setActiveInput,
setFormValue: this.props.setFormValue,
noUserInput,
blockingInputControls
}}
/>
</div>
{activeInput === target.dottedName &&
!conversationStarted && (
return (
<div>
<ul id="targets">
{displayedTargets.map(target => (
<li key={target.name}>
<div className="main">
<Header
{...{
match,
target,
conversationStarted,
isActiveInput: activeInput === target.dottedName,
blockingInputControls
}}
/>
<TargetInputOrValue
{...{
target,
targets,
activeInput,
setActiveInput,
setFormValue: this.props.setFormValue,
noUserInput,
blockingInputControls
}}
/>
</div>
{activeInput === target.dottedName && !conversationStarted && (
<InputSuggestions
suggestions={target.suggestions}
onFirstClick={value =>
@ -146,13 +148,14 @@ class TargetSelection extends Component {
colouredBackground={true}
/>
)}
</li>
))}
</ul>
</div>
)
</li>
))}
</ul>
</div>
)
}
}
}
)
let Header = ({
target,
@ -167,20 +170,18 @@ let Header = ({
encodeRuleName(target.dottedName)
return (
<span className="header">
{conversationStarted &&
!blockingInputControls && (
<ProgressCircle target={target} isActiveInput={isActiveInput} />
)}
{conversationStarted && !blockingInputControls && (
<ProgressCircle target={target} isActiveInput={isActiveInput} />
)}
<span className="texts">
{!conversationStarted &&
target.dottedName.includes('net après impôt') && (
<div>
<span id="labelNew">
<Trans>Janvier 2019</Trans>
</span>
</div>
)}
{!conversationStarted && target.dottedName.includes('net après impôt') && (
<div>
<span id="labelNew">
<Trans>Janvier 2019</Trans>
</span>
</div>
)}
<span className="optionTitle">
<Link to={ruleLink}>{target.title || target.name}</Link>
</span>
@ -238,75 +239,83 @@ let TargetInputOrValue = withLanguage(
</span>
)
)
@connect(
const TargetValue = connect(
null,
dispatch => ({
setFormValue: (field, name) => dispatch(change('conversation', field, name))
})
)
class TargetValue extends Component {
render() {
let { targets, target, noUserInput, blockingInputControls } = this.props
)(
class TargetValue extends Component {
render() {
let { targets, target, noUserInput, blockingInputControls } = this.props
let targetWithValue =
targets && targets.find(propEq('dottedName', target.dottedName)),
value = targetWithValue && targetWithValue.nodeValue
let targetWithValue =
targets && targets.find(propEq('dottedName', target.dottedName)),
value = targetWithValue && targetWithValue.nodeValue
return (
<div
className={classNames({
editable: target.question,
attractClick:
target.question && (noUserInput || blockingInputControls)
})}
tabIndex="0"
onClick={this.showField(value)}
onFocus={this.showField(value)}>
<AnimatedTargetValue value={value} />
</div>
)
}
showField(value) {
let { target, setFormValue, activeInput, setActiveInput } = this.props
return () => {
if (!target.question) return
if (value != null) setFormValue(target.dottedName, Math.floor(value) + '')
return (
<div
className={classNames({
editable: target.question,
attractClick:
target.question && (noUserInput || blockingInputControls)
})}
tabIndex="0"
onClick={this.showField(value)}
onFocus={this.showField(value)}>
<AnimatedTargetValue value={value} />
</div>
)
}
showField(value) {
let { target, setFormValue, activeInput, setActiveInput } = this.props
return () => {
if (!target.question) return
if (value != null)
setFormValue(target.dottedName, Math.floor(value) + '')
if (activeInput) setFormValue(activeInput, '')
setActiveInput(target.dottedName)
if (activeInput) setFormValue(activeInput, '')
setActiveInput(target.dottedName)
}
}
}
}
)
@withColours
@withRouter
@connect(state => ({ analysis: analysisWithDefaultsSelector(state) }))
class AidesGlimpse extends Component {
render() {
let targets = this.props.analysis.targets,
aides =
targets &&
targets.find(t => t.dottedName === 'contrat salarié . aides employeur')
if (!aides || !aides.nodeValue) return null
return (
<div id="aidesGlimpse">
{' '}
- <AnimatedTargetValue value={aides.nodeValue} />{' '}
<Link
to={
normalizeBasePath(this.props.match.path).replace(
/simulation\/$/,
''
) +
'règle/' +
encodeRuleName('contrat salarié . aides employeur')
}
style={{ color: this.props.colours.textColour }}>
<Trans>d'aides</Trans> {emoji(aides.icon)}
</Link>
</div>
)
const AidesGlimpse = compose(
withColours,
withRouter,
connect(state => ({ analysis: analysisWithDefaultsSelector(state) }))
)(
class AidesGlimpse extends Component {
render() {
let targets = this.props.analysis.targets,
aides =
targets &&
targets.find(
t => t.dottedName === 'contrat salarié . aides employeur'
)
if (!aides || !aides.nodeValue) return null
return (
<div id="aidesGlimpse">
{' '}
- <AnimatedTargetValue value={aides.nodeValue} />{' '}
<Link
to={
normalizeBasePath(this.props.match.path).replace(
/simulation\/$/,
''
) +
'règle/' +
encodeRuleName('contrat salarié . aides employeur')
}
style={{ color: this.props.colours.textColour }}>
<Trans>d'aides</Trans> {emoji(aides.icon)}
</Link>
</div>
)
}
}
}
)
export default TargetSelection

View File

@ -1,7 +1,8 @@
import Scroll from 'Components/utils/Scroll'
import { getInputComponent } from 'Engine/generateQuestions'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { reduxForm } from 'redux-form'
import {
@ -12,32 +13,35 @@ import * as Animate from 'Ui/animate'
import Aide from '../Aide'
import './conversation.css'
@reduxForm({
form: 'conversation',
destroyOnUnmount: false
})
@translate()
@connect(state => ({
conversationStarted: state.conversationStarted,
themeColours: state.themeColours,
flatRules: flatRulesSelector(state),
currentQuestion: currentQuestionSelector(state)
}))
export default class Conversation extends Component {
render() {
let { currentQuestion, flatRules } = this.props
return (
<div className="conversationContainer">
<Aide />
<div id="currentQuestion">
{currentQuestion && (
<Animate.fadeIn>
<Scroll.toElement onlyIfNotVisible />
{getInputComponent(flatRules)(currentQuestion)}
</Animate.fadeIn>
)}
export default compose(
reduxForm({
form: 'conversation',
destroyOnUnmount: false
}),
withI18n(),
connect(state => ({
conversationStarted: state.conversationStarted,
themeColours: state.themeColours,
flatRules: flatRulesSelector(state),
currentQuestion: currentQuestionSelector(state)
}))
)(
class Conversation extends Component {
render() {
let { currentQuestion, flatRules } = this.props
return (
<div className="conversationContainer">
<Aide />
<div id="currentQuestion">
{currentQuestion && (
<Animate.fadeIn>
<Scroll.toElement onlyIfNotVisible />
{getInputComponent(flatRules)(currentQuestion)}
</Animate.fadeIn>
)}
</div>
</div>
</div>
)
)
}
}
}
)

View File

@ -1,68 +1,73 @@
import { EXPLAIN_VARIABLE } from 'Actions/actions'
import classNames from 'classnames'
import { findRuleByDottedName } from 'Engine/rules'
import { compose } from 'ramda'
import React from 'react'
import { connect } from 'react-redux'
import { flatRulesSelector } from 'Selectors/analyseSelectors'
import withTracker from '../utils/withTracker'
import './Explicable.css'
@connect(
state => ({
explained: state.explainedVariable,
textColourOnWhite: state.themeColours.textColourOnWhite,
flatRules: flatRulesSelector(state)
}),
dispatch => ({
explain: variableName => dispatch({ type: EXPLAIN_VARIABLE, variableName })
})
)
@withTracker
export default class Explicable extends React.Component {
render() {
let {
flatRules,
dottedName,
explain,
explained,
tracker,
textColourOnWhite
} = this.props
export default compose(
connect(
state => ({
explained: state.explainedVariable,
textColourOnWhite: state.themeColours.textColourOnWhite,
flatRules: flatRulesSelector(state)
}),
dispatch => ({
explain: variableName =>
dispatch({ type: EXPLAIN_VARIABLE, variableName })
})
),
withTracker
)(
class Explicable extends React.Component {
render() {
let {
flatRules,
dottedName,
explain,
explained,
tracker,
textColourOnWhite
} = this.props
// Rien à expliquer ici, ce n'est pas une règle
if (dottedName == null) return null
// Rien à expliquer ici, ce n'est pas une règle
if (dottedName == null) return null
let rule = findRuleByDottedName(flatRules, dottedName)
let rule = findRuleByDottedName(flatRules, dottedName)
if (rule.description == null) return null
if (rule.description == null) return null
//TODO montrer les variables de type 'une possibilité'
//TODO montrer les variables de type 'une possibilité'
return (
<span
className={classNames('explicable', {
explained: dottedName === explained
})}>
return (
<span
className="icon"
onClick={e => {
tracker.push(['trackEvent', 'help', dottedName])
explain(dottedName)
e.preventDefault()
e.stopPropagation()
}}
style={
dottedName === explained
? {
opacity: 1,
background: textColourOnWhite,
color: 'white'
}
: { color: textColourOnWhite }
}>
<i className="fa fa-book" aria-hidden="true" />
className={classNames('explicable', {
explained: dottedName === explained
})}>
<span
className="icon"
onClick={e => {
tracker.push(['trackEvent', 'help', dottedName])
explain(dottedName)
e.preventDefault()
e.stopPropagation()
}}
style={
dottedName === explained
? {
opacity: 1,
background: textColourOnWhite,
color: 'white'
}
: { color: textColourOnWhite }
}>
<i className="fa fa-book" aria-hidden="true" />
</span>
</span>
</span>
)
)
}
}
}
)

View File

@ -1,6 +1,7 @@
import { findRuleByDottedName } from 'Engine/rules'
import { compose } from 'ramda'
import React from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import {
flatRulesSelector,
@ -9,40 +10,44 @@ import {
import { LinkButton } from 'Ui/Button'
import { capitalise0 } from '../../utils'
@translate()
@connect(
state => ({
flatRules: flatRulesSelector(state),
situation: validatedSituationSelector(state)
}),
dispatch => ({
stepAction: (name, step, source) =>
dispatch({ type: 'STEP_ACTION', name, step, source })
})
)
export default class FoldedStep extends React.Component {
render() {
let { stepAction, dottedName, flatRules, t, situation } = this.props
let { title } = findRuleByDottedName(flatRules, dottedName),
answer = situation[dottedName],
eventualEnumAnswerRule = findRuleByDottedName(
flatRules,
dottedName + ' . ' + answer
),
translatedAnswer =
(eventualEnumAnswerRule && eventualEnumAnswerRule.title) || t(answer)
export default compose(
withI18n(),
connect(
state => ({
flatRules: flatRulesSelector(state),
situation: validatedSituationSelector(state)
}),
dispatch => ({
stepAction: (name, step, source) =>
dispatch({ type: 'STEP_ACTION', name, step, source })
})
)
)(
class FoldedStep extends React.Component {
render() {
let { stepAction, dottedName, flatRules, t, situation } = this.props
let { title } = findRuleByDottedName(flatRules, dottedName),
answer = situation[dottedName],
eventualEnumAnswerRule = findRuleByDottedName(
flatRules,
dottedName + ' . ' + answer
),
translatedAnswer =
(eventualEnumAnswerRule && eventualEnumAnswerRule.title) || t(answer)
return (
<div className="foldedQuestion">
<span className="borderWrapper">
<span className="title">{capitalise0(title)}</span>
<span className="answer">{translatedAnswer}</span>
</span>
<LinkButton onClick={() => stepAction('unfold', dottedName, 'unfold')}>
<i className="fa fa-pencil" aria-hidden="true" />
<Trans>Modifier</Trans>
</LinkButton>
</div>
)
return (
<div className="foldedQuestion">
<span className="borderWrapper">
<span className="title">{capitalise0(title)}</span>
<span className="answer">{translatedAnswer}</span>
</span>
<LinkButton
onClick={() => stepAction('unfold', dottedName, 'unfold')}>
<i className="fa fa-pencil" aria-hidden="true" />
<Trans>Modifier</Trans>
</LinkButton>
</div>
)
}
}
}
)

View File

@ -9,7 +9,7 @@ import { LinkButton } from 'Ui/Button'
import './conversation.css'
import FoldedStep from './FoldedStep'
@connect(
export default connect(
state => ({
foldedSteps: state.conversationSteps.foldedSteps,
targetNames: state.targetNames,
@ -19,28 +19,29 @@ import FoldedStep from './FoldedStep'
resetSimulation,
resetForm: () => reset('conversation')
}
)
export default class FoldedSteps extends Component {
handleSimulationReset = () => {
this.props.resetSimulation()
this.props.resetForm()
}
render() {
let { foldedSteps } = this.props
)(
class FoldedSteps extends Component {
handleSimulationReset = () => {
this.props.resetSimulation()
this.props.resetForm()
}
render() {
let { foldedSteps } = this.props
if (isEmpty(foldedSteps || [])) return null
return (
<div id="foldedSteps">
<div className="header">
<LinkButton onClick={this.handleSimulationReset}>
<i className="fa fa-trash" aria-hidden="true" />
<Trans i18nKey="resetAll">Tout effacer</Trans>
</LinkButton>
if (isEmpty(foldedSteps || [])) return null
return (
<div id="foldedSteps">
<div className="header">
<LinkButton onClick={this.handleSimulationReset}>
<i className="fa fa-trash" aria-hidden="true" />
<Trans i18nKey="resetAll">Tout effacer</Trans>
</LinkButton>
</div>
{foldedSteps.map(dottedName => (
<FoldedStep key={dottedName} dottedName={dottedName} />
))}
</div>
{foldedSteps.map(dottedName => (
<FoldedStep key={dottedName} dottedName={dottedName} />
))}
</div>
)
)
}
}
}
)

View File

@ -1,7 +1,8 @@
import classNames from 'classnames'
import Explicable from 'Components/conversation/Explicable'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { change, Field } from 'redux-form'
import IgnoreStepButton from './IgnoreStepButton'
@ -20,83 +21,86 @@ to understand those precious higher order components.
*/
export var FormDecorator = formType => RenderField =>
@connect(
//... this helper directly to the redux state to avoid passing more props
state => ({
themeColours: state.themeColours,
flatRules: state.flatRules
}),
dispatch => ({
stepAction: (name, step, source) =>
dispatch({ type: 'STEP_ACTION', name, step, source }),
setFormValue: (field, value) =>
dispatch(change('conversation', field, value))
})
)
@translate()
class extends Component {
state = {
helpVisible: false
}
render() {
let {
setFormValue,
stepAction,
subquestion,
valueType,
defaultValue,
fieldName,
inversion,
themeColours
} = this.props
compose(
connect(
//... this helper directly to the redux state to avoid passing more props
state => ({
themeColours: state.themeColours,
flatRules: state.flatRules
}),
dispatch => ({
stepAction: (name, step, source) =>
dispatch({ type: 'STEP_ACTION', name, step, source }),
setFormValue: (field, value) =>
dispatch(change('conversation', field, value))
})
),
withI18n()
)(
class extends Component {
state = {
helpVisible: false
}
render() {
let {
setFormValue,
stepAction,
subquestion,
valueType,
defaultValue,
fieldName,
inversion,
themeColours
} = this.props
let validate = buildValidationFunction(valueType)
let validate = buildValidationFunction(valueType)
let submit = cause => stepAction('fold', fieldName, cause),
stepProps = {
...this.props,
submit,
validate,
setFormValue: (value, name = fieldName) => setFormValue(name, value)
}
let submit = cause => stepAction('fold', fieldName, cause),
stepProps = {
...this.props,
submit,
validate,
setFormValue: (value, name = fieldName) => setFormValue(name, value)
}
return (
<div className={classNames('step', formType)}>
<div className="unfoldedHeader">
<div className="step-question">
<h1>
{' '}
{this.props.question}{' '}
{!inversion && <Explicable dottedName={fieldName} />}
</h1>
<div
className="step-subquestion"
dangerouslySetInnerHTML={{ __html: subquestion }}
/>
return (
<div className={classNames('step', formType)}>
<div className="unfoldedHeader">
<div className="step-question">
<h1>
{' '}
{this.props.question}{' '}
{!inversion && <Explicable dottedName={fieldName} />}
</h1>
<div
className="step-subquestion"
dangerouslySetInnerHTML={{ __html: subquestion }}
/>
</div>
</div>
{defaultValue != null && (
<IgnoreStepButton
action={() => {
setFormValue(
fieldName,
typeof defaultValue == 'object'
? JSON.stringify(defaultValue)
: '' + defaultValue
)
submit('ignore')
}}
/>
)}
<fieldset>
<Field
component={RenderField}
name={fieldName}
{...stepProps}
themeColours={themeColours}
/>
</fieldset>
</div>
{defaultValue != null && (
<IgnoreStepButton
action={() => {
setFormValue(
fieldName,
typeof defaultValue == 'object'
? JSON.stringify(defaultValue)
: '' + defaultValue
)
submit('ignore')
}}
/>
)}
<fieldset>
<Field
component={RenderField}
name={fieldName}
{...stepProps}
themeColours={themeColours}
/>
</fieldset>
</div>
)
)
}
}
}
)

View File

@ -1,35 +1,41 @@
import HoverDecorator from 'Components/utils/HoverDecorator'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import './IgnoreStepButton.css'
@HoverDecorator
@translate()
export default class IgnoreStepButton extends Component {
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
export default compose(
HoverDecorator,
withI18n()
)(
class IgnoreStepButton extends Component {
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
window.addEventListener('keydown', this.boundHandleKeyDown)
window.addEventListener('keydown', this.boundHandleKeyDown)
}
handleKeyDown({ key }) {
if (key !== 'Escape') return
document.activeElement.blur()
this.props.action()
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
render() {
return (
<div id="ignore">
<a id="ignoreButton" onClick={this.props.action}>
<Trans>passer</Trans>
</a>
<span
className="keyIcon"
style={{ opacity: this.props.hover ? 1 : 0 }}>
<Trans>Échap</Trans>
</span>
</div>
)
}
}
handleKeyDown({ key }) {
if (key !== 'Escape') return
document.activeElement.blur()
this.props.action()
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
render() {
return (
<div id="ignore">
<a id="ignoreButton" onClick={this.props.action}>
<Trans>passer</Trans>
</a>
<span className="keyIcon" style={{ opacity: this.props.hover ? 1 : 0 }}>
<Trans>Échap</Trans>
</span>
</div>
)
}
}
)

View File

@ -1,66 +1,70 @@
import classnames from 'classnames'
import withColours from 'Components/utils/withColours'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { FormDecorator } from './FormDecorator'
import InputSuggestions from './InputSuggestions'
import SendButton from './SendButton'
@FormDecorator('input')
@translate()
@withColours
export default class Input extends Component {
render() {
let {
input,
dottedName,
submit,
valueType,
meta: { dirty, error, active },
t,
colours
} = this.props,
answerSuffix = valueType.suffix,
suffixed = answerSuffix != null,
inputError = dirty && error,
submitDisabled = !dirty || inputError
export default compose(
FormDecorator('input'),
withI18n(),
withColours
)(
class Input extends Component {
render() {
let {
input,
dottedName,
submit,
valueType,
meta: { dirty, error, active },
t,
colours
} = this.props,
answerSuffix = valueType.suffix,
suffixed = answerSuffix != null,
inputError = dirty && error,
submitDisabled = !dirty || inputError
return (
<span>
<div className="answer">
<input
ref={el => {
this.inputElement = el
}}
type="text"
{...input}
className={classnames({ suffixed })}
id={'step-' + dottedName}
inputMode="numeric"
placeholder={t('votre réponse')}
style={
!active
? { border: '2px dashed #ddd' }
: { border: `1px solid ${colours.textColourOnWhite}` }
}
return (
<span>
<div className="answer">
<input
ref={el => {
this.inputElement = el
}}
type="text"
{...input}
className={classnames({ suffixed })}
id={'step-' + dottedName}
inputMode="numeric"
placeholder={t('votre réponse')}
style={
!active
? { border: '2px dashed #ddd' }
: { border: `1px solid ${colours.textColourOnWhite}` }
}
/>
{suffixed && (
<label
className="suffix"
htmlFor={'step-' + dottedName}
style={!active ? { color: '#888' } : { color: '#222' }}>
{answerSuffix}
</label>
)}
<SendButton {...{ disabled: submitDisabled, error, submit }} />
</div>
<InputSuggestions
suggestions={this.props.suggestions}
onFirstClick={value => this.props.setFormValue('' + value)}
onSecondClick={() => this.props.submit('suggestion')}
/>
{suffixed && (
<label
className="suffix"
htmlFor={'step-' + dottedName}
style={!active ? { color: '#888' } : { color: '#222' }}>
{answerSuffix}
</label>
)}
<SendButton {...{ disabled: submitDisabled, error, submit }} />
</div>
<InputSuggestions
suggestions={this.props.suggestions}
onFirstClick={value => this.props.setFormValue('' + value)}
onSecondClick={() => this.props.submit('suggestion')}
/>
{inputError && <span className="step-input-error">{error}</span>}
</span>
)
{inputError && <span className="step-input-error">{error}</span>}
</span>
)
}
}
}
)

View File

@ -1,49 +1,52 @@
import withColours from 'Components/utils/withColours'
import { toPairs } from 'ramda'
import { compose, toPairs } from 'ramda'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import './InputSuggestions.css'
@withColours
@translate()
export default class InputSuggestions extends Component {
state = { suggestion: null }
render() {
let {
suggestions,
onSecondClick,
onFirstClick,
colouredBackground,
colours,
t
} = this.props
export default compose(
withColours,
withI18n()
)(
class InputSuggestions extends Component {
state = { suggestion: null }
render() {
let {
suggestions,
onSecondClick,
onFirstClick,
colouredBackground,
colours,
t
} = this.props
if (!suggestions) return null
return (
<div className="inputSuggestions">
suggestions:
<ul>
{toPairs(suggestions).map(([text, value]) => (
<li
key={value}
onClick={() => {
onFirstClick(value)
if (this.state.suggestion !== value)
this.setState({ suggestion: value })
else onSecondClick && onSecondClick(value)
}}
style={{
color: colouredBackground
? colours.textColour
: colours.textColourOnWhite
}}>
<span title={t('cliquez pour insérer cette suggestion')}>
{text}
</span>
</li>
))}
</ul>
</div>
)
if (!suggestions) return null
return (
<div className="inputSuggestions">
suggestions:
<ul>
{toPairs(suggestions).map(([text, value]) => (
<li
key={value}
onClick={() => {
onFirstClick(value)
if (this.state.suggestion !== value)
this.setState({ suggestion: value })
else onSecondClick && onSecondClick(value)
}}
style={{
color: colouredBackground
? colours.textColour
: colours.textColourOnWhite
}}>
<span title={t('cliquez pour insérer cette suggestion')}>
{text}
</span>
</li>
))}
</ul>
</div>
)
}
}
}
)

View File

@ -1,13 +1,14 @@
import HoverDecorator from 'Components/utils/HoverDecorator'
import withColours from 'Components/utils/withColours'
import { is } from 'ramda'
import { compose, is } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import Explicable from './Explicable'
import { FormDecorator } from './FormDecorator'
import './Question.css'
import SendButton from './SendButton'
import { answer, answered } from './userAnswerButtonStyle'
/* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans une liste, ou une liste de listes.
Les données @choices sont un arbre de type:
- nom: motif CDD # La racine, unique, qui formera la Question. Ses enfants sont les choix possibles
@ -26,87 +27,88 @@ import { answer, answered } from './userAnswerButtonStyle'
// FormDecorator permet de factoriser du code partagé par les différents types de saisie,
// dont Question est un example
@FormDecorator('question')
@translate()
@withColours
export default class Question extends Component {
render() {
let {
choices,
submit,
colours,
meta: { pristine }
} = this.props
let choiceElements = is(Array)(choices)
? this.renderBinaryQuestion()
: this.renderChildren(choices)
return (
<>
{choiceElements}
<SendButton
{...{
disabled: pristine,
colours,
error: false,
submit
}}
/>
</>
)
}
renderBinaryQuestion() {
let {
input, // vient de redux-form
submit,
choices,
setFormValue,
colours
} = this.props
return (
<ul className="binaryQuestionList">
{choices.map(({ value, label }) => (
<RadioLabel
key={value}
{...{ value, label, input, submit, colours, setFormValue }}
export default compose(
FormDecorator('question'),
withI18n(),
withColours
)(
class Question extends Component {
render() {
let {
choices,
submit,
colours,
meta: { pristine }
} = this.props
let choiceElements = is(Array)(choices)
? this.renderBinaryQuestion()
: this.renderChildren(choices)
return (
<>
{choiceElements}
<SendButton
{...{
disabled: pristine,
colours,
error: false,
submit
}}
/>
))}
</ul>
)
}
renderChildren(choices) {
let {
</>
)
}
renderBinaryQuestion() {
let {
input, // vient de redux-form
submit,
choices,
setFormValue,
colours
} = this.props,
{ name } = input,
// seront stockées ainsi dans le state :
// [parent object path]: dotted name relative to parent
relativeDottedName = radioDottedName =>
radioDottedName.split(name + ' . ')[1]
} = this.props
return (
<ul>
{choices.canGiveUp && (
<li key="aucun" className="variantLeaf aucun">
return (
<ul className="binaryQuestionList">
{choices.map(({ value, label }) => (
<RadioLabel
{...{
value: 'non',
label: 'Aucun',
input,
submit,
colours,
dottedName: null,
setFormValue
}}
key={value}
{...{ value, label, input, submit, colours, setFormValue }}
/>
</li>
)}
{choices.children &&
choices.children.map(
({ name, title, dottedName, children }) =>
))}
</ul>
)
}
renderChildren(choices) {
let {
input, // vient de redux-form
submit,
setFormValue,
colours
} = this.props,
{ name } = input,
// seront stockées ainsi dans le state :
// [parent object path]: dotted name relative to parent
relativeDottedName = radioDottedName =>
radioDottedName.split(name + ' . ')[1]
return (
<ul>
{choices.canGiveUp && (
<li key="aucun" className="variantLeaf aucun">
<RadioLabel
{...{
value: 'non',
label: 'Aucun',
input,
submit,
colours,
dottedName: null,
setFormValue
}}
/>
</li>
)}
{choices.children &&
choices.children.map(({ name, title, dottedName, children }) =>
children ? (
<li key={name} className="variant">
<div>{title}</div>
@ -127,11 +129,12 @@ export default class Question extends Component {
/>
</li>
)
)}
</ul>
)
)}
</ul>
)
}
}
}
)
let RadioLabel = props => (
<>
@ -140,32 +143,35 @@ let RadioLabel = props => (
</>
)
@HoverDecorator
@translate()
@withColours
class RadioLabelContent extends Component {
click = value => () => {
if (this.props.input.value == value) this.props.submit('dblClick')
}
render() {
let { value, label, input, hover, colours } = this.props,
// value = when(is(Object), prop('value'))(choice),
labelStyle = Object.assign(
value === input.value || hover ? answered(colours) : answer(colours),
value === '_' ? { fontWeight: 'bold' } : null
)
const RadioLabelContent = compose(
HoverDecorator,
withI18n(),
withColours
)(
class RadioLabelContent extends Component {
click = value => () => {
if (this.props.input.value == value) this.props.submit('dblClick')
}
render() {
let { value, label, input, hover, colours } = this.props,
// value = when(is(Object), prop('value'))(choice),
labelStyle = Object.assign(
value === input.value || hover ? answered(colours) : answer(colours),
value === '_' ? { fontWeight: 'bold' } : null
)
return (
<label key={value} style={labelStyle} className="radio">
<Trans i18nKey={`radio_${label}`}>{label}</Trans>
<input
type="radio"
{...input}
onClick={this.click(value)}
value={value}
checked={value === input.value ? 'checked' : ''}
/>
</label>
)
return (
<label key={value} style={labelStyle} className="radio">
<Trans i18nKey={`radio_${label}`}>{label}</Trans>
<input
type="radio"
{...input}
onClick={this.click(value)}
value={value}
checked={value === input.value ? 'checked' : ''}
/>
</label>
)
}
}
}
)

View File

@ -1,23 +1,24 @@
import FormDecorator from 'Components/conversation/FormDecorator'
import React, { Component } from 'react'
import { FormDecorator } from './FormDecorator'
import { answer } from './userAnswerButtonStyle'
@FormDecorator('rhetorical-question')
export default class RhetoricalQuestion extends Component {
render() {
let { input, submit, possibleChoice, themeColours } = this.props
export default FormDecorator('rhetorical-question')(
class RhetoricalQuestion extends Component {
render() {
let { input, submit, possibleChoice, themeColours } = this.props
if (!possibleChoice) return null // No action possible, don't render an answer
if (!possibleChoice) return null // No action possible, don't render an answer
let { text, value } = possibleChoice
let { text, value } = possibleChoice
return (
<span className="answer">
<label key={value} className="radio" style={answer(themeColours)}>
<input type="radio" {...input} onClick={submit} value={value} />
{text}
</label>
</span>
)
return (
<span className="answer">
<label key={value} className="radio" style={answer(themeColours)}>
<input type="radio" {...input} onClick={submit} value={value} />
{text}
</label>
</span>
)
}
}
}
)

View File

@ -1,51 +1,55 @@
import HoverDecorator from 'Components/utils/HoverDecorator'
import withColours from 'Components/utils/withColours'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
@HoverDecorator
@translate()
@withColours
export default class SendButton extends Component {
getAction() {
let { disabled, submit } = this.props
return cause => (!disabled ? submit(cause) : null)
}
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
export default compose(
HoverDecorator,
withI18n(),
withColours
)(
class SendButton extends Component {
getAction() {
let { disabled, submit } = this.props
return cause => (!disabled ? submit(cause) : null)
}
componentDidMount() {
// removeEventListener will need the exact same function instance
this.boundHandleKeyDown = this.handleKeyDown.bind(this)
window.addEventListener('keydown', this.boundHandleKeyDown)
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
handleKeyDown({ key }) {
if (key !== 'Enter') return
this.getAction()('enter')
}
render() {
let { disabled, colours, hover } = this.props
return (
<span className="sendWrapper">
<button
className="send"
disabled={disabled}
style={{
color: colours.textColour,
background: colours.colour
}}
onClick={() => this.getAction()('accept')}>
<span className="text">
<Trans>valider</Trans>
window.addEventListener('keydown', this.boundHandleKeyDown)
}
componentWillUnmount() {
window.removeEventListener('keydown', this.boundHandleKeyDown)
}
handleKeyDown({ key }) {
if (key !== 'Enter') return
this.getAction()('enter')
}
render() {
let { disabled, colours, hover } = this.props
return (
<span className="sendWrapper">
<button
className="send"
disabled={disabled}
style={{
color: colours.textColour,
background: colours.colour
}}
onClick={() => this.getAction()('accept')}>
<span className="text">
<Trans>valider</Trans>
</span>
</button>
<span
className="keyIcon"
style={{ opacity: hover && !disabled ? 1 : 0 }}>
<Trans>Entrée</Trans>
</span>
</button>
<span
className="keyIcon"
style={{ opacity: hover && !disabled ? 1 : 0 }}>
<Trans>Entrée</Trans>
</span>
</span>
)
)
}
}
}
)

View File

@ -1,52 +1,57 @@
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { FormDecorator } from './FormDecorator'
@FormDecorator('text-area')
@translate()
export default class Input extends Component {
render() {
let {
name,
input,
submit,
attributes,
meta: { touched, error },
themeColours
} = this.props,
inputError = touched && error,
sendButtonDisabled = !input.value || inputError
export default compose(
FormDecorator('text-area'),
withI18n()
)(
class Input extends Component {
render() {
let {
name,
input,
submit,
attributes,
meta: { touched, error },
themeColours
} = this.props,
inputError = touched && error,
sendButtonDisabled = !input.value || inputError
return (
<span>
<span className="answer">
<textarea
{...attributes}
{...input}
id={'step-' + name}
onKeyDown={
({ key, ctrlKey }) =>
key == 'Enter' &&
ctrlKey &&
input.value &&
(!error ? submit() : input.onBlur()) // blur will trigger the error
}
/>
<button
className="send"
style={{
visibility: sendButtonDisabled ? 'hidden' : 'visible',
color: themeColours.textColour,
background: themeColours.colour
}}
onClick={() => (!error ? submit() : null)}
>
<span className="text"><Trans>valider</Trans></span>
<span className="icon"></span>
</button>
return (
<span>
<span className="answer">
<textarea
{...attributes}
{...input}
id={'step-' + name}
onKeyDown={
({ key, ctrlKey }) =>
key == 'Enter' &&
ctrlKey &&
input.value &&
(!error ? submit() : input.onBlur()) // blur will trigger the error
}
/>
<button
className="send"
style={{
visibility: sendButtonDisabled ? 'hidden' : 'visible',
color: themeColours.textColour,
background: themeColours.colour
}}
onClick={() => (!error ? submit() : null)}>
<span className="text">
<Trans>valider</Trans>
</span>
<span className="icon"></span>
</button>
</span>
{inputError && <span className="step-input-error">{error}</span>}
</span>
{inputError && <span className="step-input-error">{error}</span>}
</span>
)
)
}
}
}
)

View File

@ -24,39 +24,39 @@ let getOptions = input =>
return { options: [] }
})
@FormDecorator('select')
class Select extends Component {
render() {
let {
input: { onChange },
submit
} = this.props,
submitOnChange = option => {
// serialize to not mix our data schema and the API response's
onChange(JSON.stringify(option))
submit()
}
export default FormDecorator('select')(
class Select extends Component {
render() {
let {
input: { onChange },
submit
} = this.props,
submitOnChange = option => {
// serialize to not mix our data schema and the API response's
onChange(JSON.stringify(option))
submit()
}
return (
<div className="select-answer commune">
<ReactSelect.Async
onChange={submitOnChange}
labelKey="nom"
optionRenderer={({ nom, departement }) =>
nom + ` (${departement?.nom})`
}
filterOptions={options => {
// Do no filtering, just return all options
return options
}}
placeholder="Entrez le nom de commune"
noResultsText="Nous n'avons trouvé aucune commune"
searchPromptText={null}
loadingPlaceholder="Recherche en cours..."
loadOptions={getOptions}
/>
</div>
)
return (
<div className="select-answer commune">
<ReactSelect.Async
onChange={submitOnChange}
labelKey="nom"
optionRenderer={({ nom, departement }) =>
nom + ` (${departement?.nom})`
}
filterOptions={options => {
// Do no filtering, just return all options
return options
}}
placeholder="Entrez le nom de commune"
noResultsText="Nous n'avons trouvé aucune commune"
searchPromptText={null}
loadingPlaceholder="Recherche en cours..."
loadOptions={getOptions}
/>
</div>
)
}
}
}
export default Select
)

View File

@ -1,9 +1,9 @@
import React, { Component } from 'react'
import { FormDecorator } from '../FormDecorator'
import ReactSelect from 'react-select'
import SelectOption from './SelectOption.js'
import 'react-select/dist/react-select.css'
import { FormDecorator } from '../FormDecorator'
import './Select.css'
import SelectOption from './SelectOption.js'
class ReactSelectWrapper extends Component {
render() {
@ -42,8 +42,7 @@ class ReactSelectWrapper extends Component {
}
}
@FormDecorator('select')
export default class Select extends Component {
class Select extends Component {
state = {
options: null
}
@ -80,3 +79,5 @@ export default class Select extends Component {
)
}
}
export default FormDecorator('select')(Select)

View File

@ -1,34 +1,33 @@
import classNames from 'classnames'
import { makeJsx } from 'Engine/evaluation'
import knownMecanisms from 'Engine/known-mecanisms.yaml'
import classNames from 'classnames'
import { path, values } from 'ramda'
import { compose, path, values } from 'ramda'
import React from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { AttachDictionary } from '../AttachDictionary'
import './Algorithm.css'
// The showValues prop is passed as a context. It used to be delt in CSS (not(.showValues) display: none), both coexist right now
import { ShowValuesProvider } from './ShowValuesContext'
@AttachDictionary(knownMecanisms)
@translate()
export default class Algorithm extends React.Component {
render() {
let { rule, showValues } = this.props,
ruleWithoutFormula =
!rule['formule'] ||
path(['formule', 'explanation', 'une possibilité'], rule)
return (
<div id="algorithm">
<section id="rule-rules" className={classNames({ showValues })}>
<ShowValuesProvider value={showValues}>
{do {
// TODO ce let est incompréhensible !
let applicabilityMecanisms = values(rule).filter(
v => v && v['rulePropType'] == 'cond'
)
applicabilityMecanisms.length > 0 && (
export default compose(
AttachDictionary(knownMecanisms),
withI18n()
)(
class Algorithm extends React.Component {
render() {
let { rule, showValues } = this.props,
ruleWithoutFormula =
!rule['formule'] ||
path(['formule', 'explanation', 'une possibilité'], rule)
// TODO ce let est incompréhensible !
let applicabilityMecanisms = values(rule).filter(
v => v && v['rulePropType'] == 'cond'
)
return (
<div id="algorithm">
<section id="rule-rules" className={classNames({ showValues })}>
<ShowValuesProvider value={showValues}>
{applicabilityMecanisms.length > 0 && (
<section id="declenchement">
<h2>
<Trans>Déclenchement</Trans>
@ -39,19 +38,19 @@ export default class Algorithm extends React.Component {
))}
</ul>
</section>
)
}}
{!ruleWithoutFormula ? (
<section id="formule">
<h2>
<Trans>Calcul</Trans>
</h2>
{makeJsx(rule['formule'])}
</section>
) : null}
</ShowValuesProvider>
</section>
</div>
)
)}
{!ruleWithoutFormula ? (
<section id="formule">
<h2>
<Trans>Calcul</Trans>
</h2>
{makeJsx(rule['formule'])}
</section>
) : null}
</ShowValuesProvider>
</section>
</div>
)
}
}
}
)

View File

@ -1,37 +1,38 @@
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import possiblesDestinataires from 'Règles/ressources/destinataires/destinataires.yaml'
import './Destinataire.css'
@translate()
export default class Rule extends Component {
render() {
let { destinataire } = this.props,
destinataireData = possiblesDestinataires[destinataire]
export default withI18n()(
class Rule extends Component {
render() {
let { destinataire } = this.props,
destinataireData = possiblesDestinataires[destinataire]
return destinataire && destinataireData ? (
<div className="infobox__item" id="destinataire">
<h4>
<Trans>Destinataire</Trans>
&nbsp;:
</h4>
<div>
<a href={destinataireData.lien} target="_blank">
{destinataireData.image && (
<img
src={require('Règles/ressources/destinataires/' +
destinataireData.image)}
/>
return destinataire && destinataireData ? (
<div className="infobox__item" id="destinataire">
<h4>
<Trans>Destinataire</Trans>
&nbsp;:
</h4>
<div>
<a href={destinataireData.lien} target="_blank">
{destinataireData.image && (
<img
src={require('Règles/ressources/destinataires/' +
destinataireData.image)}
/>
)}
{!destinataireData.image && (
<div id="calligraphy">{destinataire}</div>
)}
</a>
{destinataireData.nom && (
<div id="destinataireName">{destinataireData.nom}</div>
)}
{!destinataireData.image && (
<div id="calligraphy">{destinataire}</div>
)}
</a>
{destinataireData.nom && (
<div id="destinataireName">{destinataireData.nom}</div>
)}
</div>
</div>
</div>
) : null
) : null
}
}
}
)

View File

@ -1,58 +1,59 @@
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import { compose } from 'ramda'
import classNames from 'classnames'
import { connect } from 'react-redux'
import './Examples.css'
import { setExample } from 'Actions/actions'
import classNames from 'classnames'
import { compose } from 'ramda'
import React, { Component } from 'react'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { capitalise0 } from '../../utils'
import './Examples.css'
@connect(
state => ({
parsedRules: state.parsedRules,
themeColours: state.themeColours
}),
dispatch => ({
setExample: compose(
dispatch,
setExample
)
})
)
@translate()
export default class Examples extends Component {
render() {
let {
situationExists,
rule,
themeColours,
setExample,
currentExample
} = this.props,
{ examples } = rule
export default compose(
connect(
state => ({
parsedRules: state.parsedRules,
themeColours: state.themeColours
}),
dispatch => ({
setExample: compose(
dispatch,
setExample
)
})
),
withI18n()
)(
class Examples extends Component {
render() {
let {
situationExists,
rule,
themeColours,
setExample,
currentExample
} = this.props,
{ examples } = rule
if (!examples) return null
return (
<div id="examples">
<h2>
<Trans i18nKey="examples">Exemples</Trans>{' '}
<small>
<Trans i18nKey="clickexample">
Cliquez sur un exemple pour le tester
</Trans>
</small>
</h2>
<ul>
{examples.map(ex => (
<Example
key={ex.nom}
{...{ ex, rule, currentExample, setExample, themeColours }}
/>
))}
</ul>
if (!examples) return null
return (
<div id="examples">
<h2>
<Trans i18nKey="examples">Exemples</Trans>{' '}
<small>
<Trans i18nKey="clickexample">
Cliquez sur un exemple pour le tester
</Trans>
</small>
</h2>
<ul>
{examples.map(ex => (
<Example
key={ex.nom}
{...{ ex, rule, currentExample, setExample, themeColours }}
/>
))}
</ul>
{situationExists &&
currentExample && (
{situationExists && currentExample && (
<div>
<button
id="injectSituation"
@ -62,10 +63,11 @@ export default class Examples extends Component {
</button>
</div>
)}
</div>
)
</div>
)
}
}
}
)
let Example = ({
ex: { nom, situation },

View File

@ -1,77 +1,80 @@
import { groupBy, toPairs } from 'ramda'
import React from 'react'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import references from 'Règles/ressources/références/références.yaml'
import { capitalise0 } from '../../utils'
import './References.css'
@translate()
export default class References extends React.Component {
state = {
showComplementary: false
}
render() {
let { refs } = this.props,
{ complementary, official = [] } = groupBy(
([, link]) => (this.findRefKey(link) ? 'official' : 'complementary')
)(toPairs(refs)),
showComplementary = this.state.showComplementary,
showComplementaryButton = !this.state.showComplementary && complementary
export default withI18n()(
class References extends React.Component {
state = {
showComplementary: false
}
render() {
let { refs } = this.props,
{ complementary, official = [] } = groupBy(([, link]) =>
this.findRefKey(link) ? 'official' : 'complementary'
)(toPairs(refs)),
showComplementary = this.state.showComplementary,
showComplementaryButton = !this.state.showComplementary && complementary
return (
<ul className="references">
{[
...official.map(this.renderRef),
official.length == 0 ? (
<li id="noOfficialReferences">
<Trans>Pas de sources officielles</Trans>
</li>
) : null,
...(showComplementaryButton
? [
<li id="complementary" key="compl">
<a
href="#/"
onClick={() => this.setState({ showComplementary: true })}>
<i className="fa fa-eye" aria-hidden="true" />
<Trans>afficher les sources complémentaires</Trans>
</a>
</li>
]
: []),
...(showComplementary ? complementary.map(this.renderRef) : [])
]}
</ul>
)
}
renderRef = ([name, link]) => {
let refKey = this.findRefKey(link),
refData = (refKey && references[refKey]) || {},
domain = this.cleanDomain(link)
return (
<ul className="references">
{[
...official.map(this.renderRef),
official.length == 0 ? (
<li id="noOfficialReferences">
<Trans>Pas de sources officielles</Trans>
</li>
) : null,
...(showComplementaryButton
? [
<li id="complementary" key="compl">
<a
href="#/"
onClick={() =>
this.setState({ showComplementary: true })
}>
<i className="fa fa-eye" aria-hidden="true" />
<Trans>afficher les sources complémentaires</Trans>
</a>
</li>
]
: []),
...(showComplementary ? complementary.map(this.renderRef) : [])
]}
</ul>
)
}
renderRef = ([name, link]) => {
let refKey = this.findRefKey(link),
refData = (refKey && references[refKey]) || {},
domain = this.cleanDomain(link)
return (
<li key={name}>
<span className="imageWrapper">
{refData.image && (
<img
src={require('Règles/ressources/références/' + refData.image)}
/>
)}
</span>
<a href={link} target="_blank">
{capitalise0(name)}
</a>
<span className="url">{domain}</span>
</li>
)
return (
<li key={name}>
<span className="imageWrapper">
{refData.image && (
<img
src={require('Règles/ressources/références/' + refData.image)}
/>
)}
</span>
<a href={link} target="_blank">
{capitalise0(name)}
</a>
<span className="url">{domain}</span>
</li>
)
}
findRefKey(link) {
return Object.keys(references).find(r => link.indexOf(r) > -1)
}
cleanDomain(link) {
return (link.indexOf('://') > -1
? link.split('/')[2]
: link.split('/')[0]
).replace('www.', '')
}
}
findRefKey(link) {
return Object.keys(references).find(r => link.indexOf(r) > -1)
}
cleanDomain(link) {
return (link.indexOf('://') > -1
? link.split('/')[2]
: link.split('/')[0]
).replace('www.', '')
}
}
)

View File

@ -7,10 +7,10 @@ import {
findRuleByDottedName,
findRuleByNamespace
} from 'Engine/rules'
import { isEmpty } from 'ramda'
import { compose, isEmpty } from 'ramda'
import React, { Component } from 'react'
import Helmet from 'react-helmet'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { reduxForm } from 'redux-form'
@ -26,112 +26,115 @@ import RuleHeader from './Header'
import References from './References'
import './Rule.css'
@connect((state, props) => ({
currentExample: state.currentExample,
flatRules: flatRulesSelector(state),
valuesToShow: !noUserInputSelector(state),
analysedRule: ruleAnalysisSelector(state, props),
analysedExample: exampleAnalysisSelector(state, props)
}))
@translate()
@withLanguage
class Rule extends Component {
render() {
let {
dottedName,
currentExample,
flatRules,
valuesToShow,
analysedExample,
analysedRule,
language
} = this.props,
flatRule = findRuleByDottedName(flatRules, dottedName)
export default compose(
connect((state, props) => ({
currentExample: state.currentExample,
flatRules: flatRulesSelector(state),
valuesToShow: !noUserInputSelector(state),
analysedRule: ruleAnalysisSelector(state, props),
analysedExample: exampleAnalysisSelector(state, props)
})),
withI18n(),
withLanguage
)(
class Rule extends Component {
render() {
let {
dottedName,
currentExample,
flatRules,
valuesToShow,
analysedExample,
analysedRule,
language
} = this.props,
flatRule = findRuleByDottedName(flatRules, dottedName)
let { type, name, title, description, question, ns, icon } = flatRule,
namespaceRules = findRuleByNamespace(flatRules, dottedName)
let { type, name, title, description, question, ns, icon } = flatRule,
namespaceRules = findRuleByNamespace(flatRules, dottedName)
let displayedRule = analysedExample || analysedRule
let showValues = valuesToShow || currentExample
let displayedRule = analysedExample || analysedRule
let showValues = valuesToShow || currentExample
return (
<div id="rule" className="ui__ container">
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
</Helmet>
<RuleHeader
{...{
ns,
type,
description,
question,
flatRule,
flatRules,
name,
title,
icon
}}
/>
return (
<div id="rule" className="ui__ container">
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
</Helmet>
<RuleHeader
{...{
ns,
type,
description,
question,
flatRule,
flatRules,
name,
title,
icon
}}
/>
<section id="rule-content">
{displayedRule.nodeValue ? (
<div id="ruleValue">
<i className="fa fa-calculator" aria-hidden="true" />{' '}
{displayedRule.format === 'euros' || displayedRule.formule
? Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR'
}).format(displayedRule.nodeValue)
: typeof displayedRule.nodeValue !== 'object'
<section id="rule-content">
{displayedRule.nodeValue ? (
<div id="ruleValue">
<i className="fa fa-calculator" aria-hidden="true" />{' '}
{displayedRule.format === 'euros' || displayedRule.formule
? Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR'
}).format(displayedRule.nodeValue)
: typeof displayedRule.nodeValue !== 'object'
? displayedRule.nodeValue
: null}
</div>
) : null}
</div>
) : null}
{displayedRule.defaultValue != null &&
typeof displayedRule.defaultValue !== 'object' ? (
<div id="ruleDefault">
Valeur par défaut : {displayedRule.defaultValue}
</div>
) : null}
{displayedRule.defaultValue != null &&
typeof displayedRule.defaultValue !== 'object' ? (
<div id="ruleDefault">
Valeur par défaut : {displayedRule.defaultValue}
</div>
) : null}
{//flatRule.question &&
// Fonctionnalité intéressante, à implémenter correctement
false && <UserInput {...{ flatRules, dottedName }} />}
{flatRule.ns && (
<Algorithm rule={displayedRule} showValues={showValues} />
)}
{flatRule.note && (
<section id="notes">
<h3>Note: </h3>
{createMarkdownDiv(flatRule.note)}
</section>
)}
<Examples
currentExample={currentExample}
situationExists={valuesToShow}
rule={displayedRule}
/>
{!isEmpty(namespaceRules) && (
<NamespaceRulesList {...{ namespaceRules }} />
)}
{this.renderReferences(flatRule)}
</section>
</div>
)
{//flatRule.question &&
// Fonctionnalité intéressante, à implémenter correctement
false && <UserInput {...{ flatRules, dottedName }} />}
{flatRule.ns && (
<Algorithm rule={displayedRule} showValues={showValues} />
)}
{flatRule.note && (
<section id="notes">
<h3>Note: </h3>
{createMarkdownDiv(flatRule.note)}
</section>
)}
<Examples
currentExample={currentExample}
situationExists={valuesToShow}
rule={displayedRule}
/>
{!isEmpty(namespaceRules) && (
<NamespaceRulesList {...{ namespaceRules }} />
)}
{this.renderReferences(flatRule)}
</section>
</div>
)
}
renderReferences = ({ références: refs }) =>
refs ? (
<div>
<h2>
<Trans>Références</Trans>
</h2>
<References refs={refs} />
</div>
) : null
}
renderReferences = ({ références: refs }) =>
refs ? (
<div>
<h2>
<Trans>Références</Trans>
</h2>
<References refs={refs} />
</div>
) : null
}
)
let NamespaceRulesList = withColours(({ namespaceRules, colours }) => (
<section>
@ -155,15 +158,14 @@ let NamespaceRulesList = withColours(({ namespaceRules, colours }) => (
</section>
))
@reduxForm({
const UserInput = reduxForm({
form: 'conversation',
destroyOnUnmount: false
})
class UserInput extends Component {
render() {
let { flatRules, dottedName } = this.props
return getInputComponent(flatRules)(dottedName)
})(
class UserInput extends Component {
render() {
let { flatRules, dottedName } = this.props
return getInputComponent(flatRules)(dottedName)
}
}
}
export default Rule
)

View File

@ -16,15 +16,15 @@ let RuleValueVignette = ({ name, title, nodeValue: ruleValue }) => (
</span>
)
@withLanguage
export class RuleValue extends Component {
render() {
let { value, language } = this.props
let unsatisfied = value == null,
irrelevant = value == 0
let [className, text] = irrelevant
? ['irrelevant', '0']
: unsatisfied
export const RuleValue = withLanguage(
class RuleValue extends Component {
render() {
let { value, language } = this.props
let unsatisfied = value == null,
irrelevant = value == 0
let [className, text] = irrelevant
? ['irrelevant', '0']
: unsatisfied
? ['unsatisfied', '']
: [
'figure',
@ -35,18 +35,19 @@ export class RuleValue extends Component {
minimumFractionDigits: 0
}).format(value)
]
return (
<ReactCSSTransitionGroup
transitionName="flash"
transitionEnterTimeout={100}
transitionLeaveTimeout={100}>
<span key={text} className="Rule-value">
{' '}
<span className={className}>{text}</span>
</span>
</ReactCSSTransitionGroup>
)
return (
<ReactCSSTransitionGroup
transitionName="flash"
transitionEnterTimeout={100}
transitionLeaveTimeout={100}>
<span key={text} className="Rule-value">
{' '}
<span className={className}>{text}</span>
</span>
</ReactCSSTransitionGroup>
)
}
}
}
)
export default RuleValueVignette

View File

@ -1,12 +1,13 @@
import withTracker from 'Components/utils/withTracker'
import { Component } from 'react'
@withTracker
export default class TrackPageView extends Component {
componentDidMount() {
this.props.tracker.push(['trackPageView'])
export default withTracker(
class TrackPageView extends Component {
componentDidMount() {
this.props.tracker.push(['trackPageView'])
}
render() {
return null
}
}
render() {
return null
}
}
)

View File

@ -1,20 +1,19 @@
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { withI18n } from 'react-i18next'
export default function withLanguage(WrappedComponent) {
return class WithLanguage extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
return withI18n()(
class WithLanguage extends Component {
static displayName = `withLanguage(${Component.displayName ||
Component.name})`
render() {
return (
<WrappedComponent
{...this.props}
language={this.props.i18n.language + ''}
/>
)
}
}
static displayName = `withLanguage(${Component.displayName ||
Component.name})`
render() {
return (
<WrappedComponent
{...this.props}
language={this.context.i18n.language + ''}
/>
)
}
}
)
}

View File

@ -15,8 +15,8 @@ let treatValue = (data, language) =>
data == null
? '?'
: typeof data == 'boolean'
? { true: '✅', false: '✘' }[data]
: formatNumber(data, language)
? { true: '✅', false: '✘' }[data]
: formatNumber(data, language)
export let formatNumber = (data, language) =>
!isNaN(data)
@ -80,28 +80,29 @@ export function InlineMecanism({ name }) {
}
// Un élément du graphe de calcul qui a une valeur interprétée (à afficher)
@connect(state => ({ flatRules: flatRulesSelector(state) }))
export class Leaf extends Component {
render() {
let { classes, dottedName, name, value, flatRules, filter } = this.props,
rule = findRuleByDottedName(flatRules, dottedName)
export const Leaf = connect(state => ({ flatRules: flatRulesSelector(state) }))(
class Leaf extends Component {
render() {
let { classes, dottedName, name, value, flatRules, filter } = this.props,
rule = findRuleByDottedName(flatRules, dottedName)
return (
<span className={classNames(classes, 'leaf')}>
{dottedName && (
<span className="nodeHead">
<Link to={'../règle/' + encodeRuleName(dottedName)}>
<span className="name">
{rule.title || capitalise0(name)} {filter}
<NodeValuePointer data={value} />
</span>
</Link>
</span>
)}
</span>
)
return (
<span className={classNames(classes, 'leaf')}>
{dottedName && (
<span className="nodeHead">
<Link to={'../règle/' + encodeRuleName(dottedName)}>
<span className="name">
{rule.title || capitalise0(name)} {filter}
<NodeValuePointer data={value} />
</span>
</Link>
</span>
)}
</span>
)
}
}
}
)
export function SimpleRuleLink({ rule: { dottedName, title, name } }) {
return (

View File

@ -21,7 +21,6 @@ let lang =
setToSessionStorage('lang', lang)
i18next.init(
{
debug: true,
lng: lang,
resources: {
en: {

View File

@ -522,3 +522,11 @@ privacyContent: >
</2>
<3>You can opt out below.</3>
Vie privée: Privacy
Nom: Name
Activité principale: Main activity
Adresse: Address
Ville: City
Région: Region
Nombre d'employés: Number of employees
Date de création: Creation date

View File

@ -3,32 +3,33 @@ import { SliderPicker } from 'react-color'
import { connect } from 'react-redux'
import Home from './Home'
@connect(
export default connect(
state => ({ couleur: state.themeColours.colour }),
dispatch => ({
changeColour: colour => dispatch({ type: 'CHANGE_THEME_COLOUR', colour })
})
)
export default class Couleur extends React.Component {
changeColour = ({ hex }) => this.props.changeColour(hex)
render() {
return (
<div className="ui__ container">
<p className="indication">
Visualisez sur cette page lapparence du module pour différentes
couleurs principales.
</p>
<SliderPicker
color={this.props.couleur}
onChangeComplete={this.changeColour}
/>
<p className="indication">
La couleur sélectionnée, à déclarer comme attribut
&quot;data-couleur&quot; du script sur votre page est :{' '}
<b>{this.props.couleur}</b>
</p>
<Home />
</div>
)
)(
class Couleur extends React.Component {
changeColour = ({ hex }) => this.props.changeColour(hex)
render() {
return (
<div className="ui__ container">
<p className="indication">
Visualisez sur cette page lapparence du module pour différentes
couleurs principales.
</p>
<SliderPicker
color={this.props.couleur}
onChangeComplete={this.changeColour}
/>
<p className="indication">
La couleur sélectionnée, à déclarer comme attribut
&quot;data-couleur&quot; du script sur votre page est :{' '}
<b>{this.props.couleur}</b>
</p>
<Home />
</div>
)
}
}
}
)

View File

@ -1,17 +1,18 @@
// Page listing the engine's currently implemented mecanisms and their tests
import React, { Component } from 'react'
import './ExampleSituations.css'
import examples from 'Règles/cas-types.yaml'
import { analyseMany } from 'Engine/traverse'
import { connect } from 'react-redux'
import {
ruleDefaultsSelector,
parsedRulesSelector
} from 'Selectors/analyseSelectors'
import withColours from 'Components/utils/withColours'
import { analyseMany } from 'Engine/traverse'
import { compose } from 'ramda'
import React, { Component } from 'react'
import emoji from 'react-easy-emoji'
import { connect } from 'react-redux'
import examples from 'Règles/cas-types.yaml'
import {
parsedRulesSelector,
ruleDefaultsSelector
} from 'Selectors/analyseSelectors'
import './ExampleSituations.css'
class ExampleSituations extends Component {
export default class ExampleSituations extends Component {
render() {
return (
<div className="ui__ container" id="exampleSituations">
@ -28,69 +29,71 @@ class ExampleSituations extends Component {
)
}
}
@connect(state => ({
defaults: ruleDefaultsSelector(state),
parsedRules: parsedRulesSelector(state)
}))
@withColours
class Example extends Component {
render() {
let {
ex: { nom, situation },
parsedRules,
defaults,
colours
} = this.props,
[total, net, netAprèsImpôts] = analyseMany(parsedRules, [
'total',
'net',
'net après impôt'
])(dottedName => ({ ...defaults, ...situation }[dottedName])).targets,
figures = [
total,
do {
let dottedName = 'contrat salarié . salaire . brut de base'
;({
dottedName,
nodeValue: situation[dottedName],
title: 'Salaire brut'
})
},
net,
{ ...netAprèsImpôts, title: 'Après impôt' }
]
return (
<li className="example">
<h2>{nom}</h2>
<ul>
{figures.map(t => (
<li key={t.dottedName}>
<h3>{t.title}</h3>
const Example = compose(
connect(state => ({
defaults: ruleDefaultsSelector(state),
parsedRules: parsedRulesSelector(state)
})),
withColours
)(
class Example extends Component {
render() {
let {
ex: { nom, situation },
parsedRules,
defaults,
colours
} = this.props,
[total, net, netAprèsImpôts] = analyseMany(parsedRules, [
'total',
'net',
'net après impôt'
])(dottedName => ({ ...defaults, ...situation }[dottedName])).targets,
figures = [
total,
do {
let dottedName = 'contrat salarié . salaire . brut de base'
;({
dottedName,
nodeValue: situation[dottedName],
title: 'Salaire brut'
})
},
net,
{ ...netAprèsImpôts, title: 'Après impôt' }
]
return (
<li className="example">
<h2>{nom}</h2>
<ul>
{figures.map(t => (
<li key={t.dottedName}>
<h3>{t.title}</h3>
<span
style={{ color: colours.textColourOnWhite }}
className="figure">
{Math.round(t.nodeValue)}
</span>
</li>
))}{' '}
<li key="%">
<h3>Prélèvements</h3>
<span
style={{ color: colours.textColourOnWhite }}
className="figure">
{Math.round(t.nodeValue)}
{do {
let de = figures[0].nodeValue,
à = figures[3].nodeValue
Math.round(((de - à) / de) * 100)
}}{' '}
%
</span>
</li>
))}{' '}
<li key="%">
<h3>Prélèvements</h3>
<span
style={{ color: colours.textColourOnWhite }}
className="figure">
{do {
let de = figures[0].nodeValue,
à = figures[3].nodeValue
Math.round(((de - à) / de) * 100)
}}{' '}
%
</span>
</li>
</ul>
</li>
)
</ul>
</li>
)
}
}
}
export default ExampleSituations
)

View File

@ -1,58 +1,60 @@
import LangSwitcher from 'Components/LangSwitcher'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
import { Link } from 'react-router-dom'
import { withI18n } from 'react-i18next'
import Logo from '../images/logo/logo-simulateur.svg'
import './Header.css'
import { compose } from 'ramda';
@withRouter
@translate()
export class Header extends Component {
state = {
mobileNavVisible: false
}
togglemobileNavVisible = () =>
this.setState({ mobileNavVisible: !this.state.mobileNavVisible })
export const Header = compose(
withRouter,
withI18n()
)(
class Header extends Component {
state = {
mobileNavVisible: false
}
togglemobileNavVisible = () =>
this.setState({ mobileNavVisible: !this.state.mobileNavVisible })
render() {
return (
<div id="header">
<Link id="brand" to="/">
<img id="logo" src={Logo} alt="Un service de l'État français" />
<h1>
Simulateur
<br />
d'embauche
</h1>
</Link>
<div id="headerRight">
<nav className={this.state.mobileNavVisible ? 'visible' : ''}>
<Links toggle={this.togglemobileNavVisible} />
</nav>
<LangSwitcher className="menu-item ui__ link-button" />
<span id="menuButton">
{this.state.mobileNavVisible ? (
<i
className="fa fa-times"
aria-hidden="true"
onClick={this.togglemobileNavVisible}
/>
) : (
<i
className="fa fa-bars"
aria-hidden="true"
onClick={this.togglemobileNavVisible}
/>
)}
</span>
render() {
return (
<div id="header">
<Link id="brand" to="/">
<img id="logo" src={Logo} alt="Un service de l'État français" />
<h1>
Simulateur
<br />
d'embauche
</h1>
</Link>
<div id="headerRight">
<nav className={this.state.mobileNavVisible ? 'visible' : ''}>
<Links toggle={this.togglemobileNavVisible} />
</nav>
<LangSwitcher className="menu-item ui__ link-button" />
<span id="menuButton">
{this.state.mobileNavVisible ? (
<i
className="fa fa-times"
aria-hidden="true"
onClick={this.togglemobileNavVisible}
/>
) : (
<i
className="fa fa-bars"
aria-hidden="true"
onClick={this.togglemobileNavVisible}
/>
)}
</span>
</div>
</div>
</div>
)
)
}
}
}
)
let Links = ({ toggle }) => (
<div id="links" onClick={toggle}>
<Link className="menu-item" to="/exemples">

View File

@ -7,10 +7,9 @@ import URSSAF from 'Images/urssaf.svg'
import './Home.css'
import { inIframe } from '../../../utils'
import emoji from 'react-easy-emoji'
import translate from 'react-i18next/dist/commonjs/translate'
import withLanguage from 'Components/utils/withLanguage'
const Home = translate()(
const Home =
withLanguage(({ language }) => (
<div id="home" className="ui__ container">
<PreviousSimulationBanner />
@ -66,6 +65,5 @@ const Home = translate()(
</p>
</div>
))
)
export default Home

View File

@ -1,29 +1,28 @@
import LangSwitcher from 'Components/LangSwitcher'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import emoji from 'react-easy-emoji'
import { Trans, withI18n } from 'react-i18next'
import screenfull from 'screenfull'
import emoji from 'react-easy-emoji';
import { isIE } from '../../../utils';
import { isIE } from '../../../utils'
@translate()
export default class IframeFooter extends Component {
componentDidMount() {
screenfull.enabled && screenfull.onchange(() => this.forceUpdate())
}
export default withI18n()(
class IframeFooter extends Component {
componentDidMount() {
screenfull.enabled && screenfull.onchange(() => this.forceUpdate())
}
render() {
return (
<div
className="ui__ container"
style={{
textAlign: 'right',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<LangSwitcher className="ui__ button simple" />
{screenfull.enabled &&
!screenfull.isFullscreen && !isIE() && (
render() {
return (
<div
className="ui__ container"
style={{
textAlign: 'right',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<LangSwitcher className="ui__ button simple" />
{screenfull.enabled && !screenfull.isFullscreen && !isIE() && (
<button
className="ui__ button small"
onClick={() => {
@ -33,11 +32,12 @@ export default class IframeFooter extends Component {
<Trans>Plein écran</Trans>
</button>
)}
<button className="ui__ button small" onClick={() =>window.print()} >
{emoji('🖨')}
<Trans>Imprimer</Trans>
</button>
</div>
)
<button className="ui__ button small" onClick={() => window.print()}>
{emoji('🖨')}
<Trans>Imprimer</Trans>
</button>
</div>
)
}
}
}
)

View File

@ -1,19 +1,20 @@
import React, { Component } from 'react'
import { withRouter } from 'react-router'
@withRouter
export default class IntegrationTest extends Component {
componentDidMount() {
const script = document.createElement('script')
script.id = 'script-simulateur-embauche'
script['data-couleur'] = script.src =
window.location.origin + '/dist/simulateur.js'
script.dataset.couleur = '#2975D1'
script.dataset.iframeUrl =
window.location.origin + this.props.history.createHref({})
this.DOMNode.appendChild(script)
export default withRouter(
class IntegrationTest extends Component {
componentDidMount() {
const script = document.createElement('script')
script.id = 'script-simulateur-embauche'
script['data-couleur'] = script.src =
window.location.origin + '/dist/simulateur.js'
script.dataset.couleur = '#2975D1'
script.dataset.iframeUrl =
window.location.origin + this.props.history.createHref({})
this.DOMNode.appendChild(script)
}
render() {
return <div ref={ref => (this.DOMNode = ref)} />
}
}
render() {
return <div ref={ref => (this.DOMNode = ref)} />
}
}
)

View File

@ -5,21 +5,22 @@ import 'react-select/dist/react-select.css'
import { flatRulesSelector } from 'Selectors/analyseSelectors'
import './RulesList.css'
@connect(state => ({
export default connect(state => ({
flatRules: flatRulesSelector(state)
}))
export default class RulesList extends Component {
render() {
let { flatRules } = this.props
return (
<div id="RulesList" className="ui__ container">
<h1>Explorez notre base de règles</h1>
<SearchBar
showDefaultList={true}
rules={flatRules}
rulePagesBasePath="règle"
/>
</div>
)
}))(
class RulesList extends Component {
render() {
let { flatRules } = this.props
return (
<div id="RulesList" className="ui__ container">
<h1>Explorez notre base de règles</h1>
<SearchBar
showDefaultList={true}
rules={flatRules}
rulePagesBasePath="règle"
/>
</div>
)
}
}
}
)

View File

@ -3,7 +3,7 @@ import { defaultTracker } from 'Components/utils/withTracker'
import createRavenMiddleware from 'raven-for-redux'
import Raven from 'raven-js'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { Route, Switch } from 'react-router-dom'
import 'Ui/index.css'
import Provider from '../../Provider'
@ -68,7 +68,7 @@ class InFranceRoute extends Component {
}
}
let RouterSwitch = translate()(() => {
let RouterSwitch = withI18n()(() => {
const paths = sitePaths()
return (
<Switch>

View File

@ -1,3 +1,5 @@
/* @flow */
import PageFeedback from 'Components/Feedback/PageFeedback'
import LegalNotice from 'Components/LegalNotice'
import withColours from 'Components/utils/withColours'
@ -6,7 +8,7 @@ import { compose } from 'ramda'
import React from 'react'
import emoji from 'react-easy-emoji'
import Helmet from 'react-helmet'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { withRouter } from 'react-router'
import i18n from '../../../../i18n'
import { feedbackBlacklist } from '../../config'
@ -80,5 +82,5 @@ const Footer = ({ colours: { colour } }) => {
export default compose(
withRouter,
withColours,
translate()
withI18n()
)(Footer)

View File

@ -2,7 +2,7 @@
import { React, T } from 'Components'
import { compose } from 'ramda'
import emoji from 'react-easy-emoji'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import selectors from 'Selectors/progressSelectors'
@ -235,7 +235,7 @@ const StepsHeader = ({
export default compose(
withRouter,
translate(),
withI18n(),
connect(
state => ({
...selectors(state),

View File

@ -3,7 +3,7 @@
import { React, T } from 'Components'
import withTracker from 'Components/utils/withTracker'
import { compose } from 'ramda'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import selectors from 'Selectors/progressSelectors'
@ -87,5 +87,5 @@ export default compose(
selectors,
{}
),
translate()
withI18n()
)(StepsHeader)

View File

@ -1,7 +1,8 @@
/* @flow */
import { React, T } from 'Components'
import { ScrollToTop } from 'Components/utils/Scroll'
import { translate } from 'react-i18next'
import { compose } from 'ramda'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import Animate from 'Ui/animate'
@ -25,7 +26,7 @@ const AfterRegistration = ({ t, companyStatusChoice }: Props) => (
Une fois que votre{' '}
{{
companyStatusChoice:
companyStatusChoice || t('après.entreprise', 'entreprise')
companyStatusChoice || t(['après.entreprise', 'entreprise'])
}}{' '}
aura été créée, vous recevrez les informations suivantes :
</T>
@ -93,6 +94,9 @@ const AfterRegistration = ({ t, companyStatusChoice }: Props) => (
</Animate.fromBottom>
)
export default connect(state => ({
companyStatusChoice: state.inFranceApp.companyStatusChoice
}))(translate()(AfterRegistration))
export default compose(
connect(state => ({
companyStatusChoice: state.inFranceApp.companyStatusChoice
})),
withI18n()
)(AfterRegistration)

View File

@ -8,7 +8,7 @@ import { React, T } from 'Components'
import Scroll from 'Components/utils/Scroll'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import * as Animate from 'Ui/animate'
@ -388,7 +388,7 @@ const CreateCompany = ({
)
}
export default compose(
translate(),
withI18n(),
connect(
state => ({
companyCreationChecklist: state.inFranceApp.companyCreationChecklist,

View File

@ -3,7 +3,7 @@ import { defineDirectorStatus } from 'Actions/companyStatusActions'
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { SkipButton } from 'Ui/Button'
import type { DirectorStatus } from 'Types/companyTypes'
@ -76,7 +76,7 @@ const DefineDirectorStatus = ({ defineDirectorStatus, t }: Props) => (
)
export default compose(
translate(),
withI18n(),
connect(
null,
{ defineDirectorStatus }

View File

@ -3,7 +3,7 @@ import { saveExistingCompanyDetails } from 'Actions/existingCompanyActions'
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
import { Link } from 'react-router-dom'
@ -121,5 +121,5 @@ export default compose(
onCompanyDetailsConfirmation: saveExistingCompanyDetails
}
),
translate()
withI18n()
)(Search)

View File

@ -3,7 +3,7 @@ import { chooseCompanyLiability } from 'Actions/companyStatusActions'
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { SkipButton } from 'Ui/Button'
import type { CompanyLiability } from 'Types/companyTypes'
@ -116,7 +116,7 @@ const Liability = ({
)
export default compose(
translate(),
withI18n(),
connect(
state => ({
multipleAssociates:

View File

@ -3,7 +3,7 @@ import { companyIsMicroenterprise } from 'Actions/companyStatusActions'
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { SkipButton } from 'Ui/Button'
import type { TFunction } from 'react-i18next'
@ -101,7 +101,7 @@ const Microenterprise = ({ companyIsMicroenterprise, t }: Props) => (
)
export default compose(
translate(),
withI18n(),
connect(
null,
{ companyIsMicroenterprise }

View File

@ -3,7 +3,7 @@ import { directorIsInAMinority } from 'Actions/companyStatusActions'
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { SkipButton } from 'Ui/Button'
import type { TFunction } from 'react-i18next'
@ -72,7 +72,7 @@ const MinorityDirector = ({ directorIsInAMinority, t }: Props) => (
)
export default compose(
translate(),
withI18n(),
connect(
null,
{ directorIsInAMinority }

View File

@ -4,7 +4,7 @@ import Helmet from 'react-helmet'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { possibleStatusSelector } from 'Selectors/companyStatusSelectors'
import {translate} from 'react-i18next';
import { withI18n } from 'react-i18next';
import StatusDescription from './StatusDescription'
import type { RouterHistory } from 'react-router'
import {compose} from 'ramda'
@ -23,7 +23,7 @@ type Props = {
t: TFunction
}
const StatusButton = translate()(({ status, t }: { status: LegalStatus, t: TFunction }) => (
const StatusButton = withI18n()(({ status, t }: { status: LegalStatus, t: TFunction }) => (
<Link to={sitePaths().entreprise.créer(status)} className="ui__ button">
<T>Créer une</T> {t(status)}
</Link>
@ -143,7 +143,7 @@ const SetMainStatus = ({ history, possibleStatus, t, language }: Props) => {
</>
)
}
export default compose(translate(), withLanguage, connect(
export default compose(withI18n(), withLanguage, connect(
state => ({ possibleStatus: possibleStatusSelector(state) }),
{ setMainStatus }
))(SetMainStatus)

View File

@ -3,20 +3,22 @@
import { React, T } from 'Components'
import withLanguage from 'Components/utils/withLanguage'
import { toPairs } from 'ramda'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Link, Redirect } from 'react-router-dom'
import sitePaths from '../../sitePaths'
import type { ResetExistingCompanyDetailsAction } from 'Types/companyTypes'
import type { TFunction } from 'react-i18next'
let companyDataSelection = {
l1_normalisee: 'Name',
libelle_activite_principale: 'Main activity',
l4_normalisee: 'Street',
l6_normalisee: 'City',
libelle_region: 'Region',
libelle_tranche_effectif_salarie_entreprise: 'Number of employees',
date_creation: 'Creation date'
}
let companyDataSelection = t => ({
l1_normalisee: t('Nom'),
libelle_activite_principale: t('Activité principale'),
l4_normalisee: t('Adresse'),
l6_normalisee: t('Ville'),
libelle_region: t('Région'),
libelle_tranche_effectif_salarie_entreprise: t("Nombre d'employés"),
date_creation: t('Date de création')
})
const YYYYMMDDToDate = (date: string): Date =>
new Date(date.replace(/^([\d]{4})([\d]{2})([\d]{2})$/, '$1/$2/$3'))
@ -36,14 +38,15 @@ const LocaleDate = withLanguage(
}).format(date)
)
export const CompanyDetails = (data: { [string]: string }) => {
return (
<ul>
{toPairs(data).map(
([key, value]) =>
companyDataSelection[key] != null ? (
export const CompanyDetails = withI18n()(
({ t, ...data }: { t: TFunction, [string]: string }) => {
const localizedCompanyDataSelection = companyDataSelection(t)
return (
<ul>
{toPairs(data).map(([key, value]) =>
localizedCompanyDataSelection[key] != null ? (
<li key={key}>
<strong>{companyDataSelection[key]}</strong>
<strong>{localizedCompanyDataSelection[key]}</strong>
<br />
{key === 'date_creation' ? (
<LocaleDate date={YYYYMMDDToDate(value)} />
@ -52,10 +55,11 @@ export const CompanyDetails = (data: { [string]: string }) => {
)}
</li>
) : null
)}
</ul>
)
}
)}
</ul>
)
}
)
const YourCompany = ({ companyDetails, resetCompanyDetails }) => (
<>

View File

@ -1,5 +1,5 @@
import React from 'react'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Redirect, Route, Switch } from 'react-router'
import * as Animate from 'Ui/animate'
@ -107,4 +107,4 @@ const CreateMyCompany = ({
export default connect(state => ({
companyStatusChoice: state.inFranceApp.companyStatusChoice,
existingCompany: state.inFranceApp.existingCompanyDetails
}))(translate()(CreateMyCompany))
}))(withI18n()(CreateMyCompany))

View File

@ -6,7 +6,7 @@ import {
import { React, T } from 'Components'
import { compose } from 'ramda'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import Animate from 'Ui/animate'
@ -198,7 +198,9 @@ const HiringProcess = ({
</li>
<li>Remettre la fiche de paie à votre employé</li>
</ul>
<Link className="ui__ button" to={sitePaths().sécuritéSociale + '/simulation'}>
<Link
className="ui__ button"
to={sitePaths().sécuritéSociale + '/simulation'}>
Obtenir un exemple de fiche de paie
</Link>
</T>
@ -206,7 +208,7 @@ const HiringProcess = ({
)
export default compose(
translate(),
withI18n(),
connect(
state => ({ hiringChecklist: state.inFranceApp.hiringChecklist }),
{

View File

@ -5,7 +5,7 @@ import marianneSvg from 'Images/marianne.svg'
import urssafSvg from 'Images/urssaf.svg'
import React from 'react'
import emoji from 'react-easy-emoji'
import { Trans, translate } from 'react-i18next'
import { Trans, withI18n } from 'react-i18next'
import { Link } from 'react-router-dom'
import companySvg from '../images/company.svg'
import estimateSvg from '../images/estimate.svg'
@ -14,7 +14,7 @@ import Footer from '../layout/Footer/Footer'
import sitePaths from '../sitePaths'
import './Landing.css'
export default translate()(
export default withI18n()(
withColours(({ colours: { colour } }) => (
<>
<header className="landing__header">

View File

@ -4,7 +4,7 @@ import { Component, React, T } from 'Components'
import Simulateur from 'Components/Simu'
import { ScrollToTop } from 'Components/utils/Scroll'
import Helmet from 'react-helmet'
import { translate } from 'react-i18next'
import { withI18n } from 'react-i18next'
import * as Animate from 'Ui/animate'
import type { Match, Location } from 'react-router'
import type { TFunction } from 'react-i18next'
@ -82,4 +82,4 @@ class SocialSecurity extends Component<Props, {}> {
}
}
export default translate()(SocialSecurity)
export default withI18n()(SocialSecurity)

616
yarn.lock

File diff suppressed because it is too large Load Diff