🎨 Format the currencies depending on the locale

Add a withLanguage HOC for abstracting the language
implementation
pull/226/head
Johan Girod 2018-04-27 15:51:31 +02:00
parent e1eb3d367c
commit 7ab7c6c7eb
22 changed files with 737 additions and 222 deletions

View File

@ -17,6 +17,12 @@ env:
browser: true
commonjs: true
es6: true
overrides:
files: "*.test.js"
env:
mocha: true
extends:
- eslint:recommended
- plugin:react/recommended

15
componentTestSetup.js Normal file
View File

@ -0,0 +1,15 @@
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import chai from 'chai'
import sinonChai from 'sinon-chai'
chai.use(sinonChai)
Enzyme.configure({ adapter: new Adapter() })
// Setup Intl in "en" and "fr" for testing
// var areIntlLocalesSupported = require('intl-locales-supported')
// var localesMyAppSupports = ['en', 'fr']
global.Intl = require('intl')
require('intl/locale-data/jsonp/en.js')
require('intl/locale-data/jsonp/fr.js')

View File

@ -1,21 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="google-site-verification" content="C03WwnrJP0FLqf83ibMBA7_N-TLQcwsJaAhqKXppxaE" />
<title>Simulateur d'embauche</title>
<meta name="description" content="Simulation du prix d'une embauche en France" data-react-helmet="true"> <!-- data-helmet pour que React Helmet puisse écraser ce meta par défaut -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:200,300,400,500,600,700' rel='stylesheet' type='text/css'>
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#2975d1">
</head>
<body>
<div id="js" />
<script src="https://use.fontawesome.com/1da10bbdec.js"></script>
<script type="text/javascript" src="/dist/bundle.js"></script>
</body>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="google-site-verification" content="C03WwnrJP0FLqf83ibMBA7_N-TLQcwsJaAhqKXppxaE" />
<title>Simulateur d'embauche</title>
<meta name="description" content="Simulation du prix d'une embauche en France" data-react-helmet="true">
<!-- data-helmet pour que React Helmet puisse écraser ce meta par défaut -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:200,300,400,500,600,700' rel='stylesheet' type='text/css'>
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#2975d1">
</head>
</html>
<body>
<div id="js" />
<script src="https://use.fontawesome.com/1da10bbdec.js"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en,Intl.~locale.fr"></script>
<script type="text/javascript" src="/dist/bundle.js"></script>
</body>
</html>

View File

@ -6,9 +6,12 @@
"url": "git@github.com:betagouv/syso.git"
},
"engines": {
"node": ">=8.10.0 <10.0.0"
"node": ">=8.10.0"
},
"browserslist": ["> 1% in FR", "not ie < 11"],
"browserslist": [
"> 1% in FR",
"not ie < 11"
],
"dependencies": {
"@babel/core": "=7.0.0-beta.46",
"@babel/plugin-proposal-decorators": "=7.0.0-beta.46",
@ -34,8 +37,6 @@
"daggy": "^1.2.0",
"dedent-js": "^1.0.1",
"deep-assign": "^2.0.0",
"eslint": "^4.19.0",
"eslint-plugin-react": "^7.7.0",
"express": "^4.16.3",
"fantasy-combinators": "0.0.1",
"fantasy-land": "^3.5.0",
@ -54,8 +55,6 @@
"json-loader": "^0.5.7",
"live-server": "^1.2.0",
"marked": "^0.3.17",
"mocha": "^5.0.4",
"mocha-webpack": "^2.0.0-beta.0",
"nearley": "^2.13.0",
"nearley-loader": "^2.0.0",
"npm": "^5.7.1",
@ -98,28 +97,31 @@
"yaml-loader": "^0.5.0"
},
"scripts": {
"pretest":
"LIST=`git diff --name-only HEAD..HEAD^ | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi",
"pretest": "LIST=`git diff --name-only HEAD..HEAD^ | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi",
"start": "node source/server.js",
"externalize": "node source/externalize.js",
"compile": "webpack --config source/webpack.prod.js",
"test":
"mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\"",
"test-watch":
"mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\" --watch",
"test-meca":
"mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/mecanisms.test.js --watch",
"test-rules":
"mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/real-rules.test.js --watch",
"test-inversions":
"mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/inversion.test.js\" --watch",
"test": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --include componentTestSetup.js --require test/helpers/browser.js \"./{,!(node_modules)/**/}!(webpack).test.js\"",
"test-watch": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/**/*.test.js\" --watch",
"test-meca": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/mecanisms.test.js --watch",
"test-rules": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js test/real-rules.test.js --watch",
"test-inversions": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --require test/helpers/browser.js \"test/inversion.test.js\" --watch",
"test-components": "mocha-webpack --webpack-config source/webpack.test.js --require source-map-support/register --include componentTestSetup.js --require test/helpers/browser.js \"source/components/**/*.test.js\" --watch",
"heroku-postbuild": "yarn install --production=false && yarn compile",
"eslint":
"LIST=`git diff --cached --name-only HEAD | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi",
"eslint-check":
"eslint --print-config .eslintrc | eslint-config-prettier-check"
"eslint": "LIST=`git diff --cached --name-only HEAD | grep .*\\.js | grep -v json`; if [ \"$LIST\" ]; then eslint $LIST; fi",
"eslint-check": "eslint --print-config .eslintrc | eslint-config-prettier-check"
},
"devDependencies": {
"eslint-config-prettier": "^2.9.0"
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^4.19.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-react": "^7.7.0",
"intl": "^1.2.5",
"intl-locales-supported": "^1.0.0",
"mocha": "^5.0.4",
"mocha-webpack": "^2.0.0-beta.0",
"sinon": "^4.5.0",
"sinon-chai": "^3.0.0"
}
}

View File

@ -0,0 +1,17 @@
.currencyInput__container {
display: flex !important;
align-items: baseline;
justify-content: flex-end;
}
.currencyInput__input {
height: inherit;
border: none;
text-align: inherit;
font-family: inherit;
font-weight: inherit;
min-width: 0;
color: inherit;
background-color: inherit;
font-size: inherit;
}

View File

@ -0,0 +1,83 @@
import React, { Component } from 'react'
import { dissoc } from 'ramda'
import classnames from 'classnames'
import './CurrencyInput.css'
let isCurrencyPrefixed = language =>
!!Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR'
})
.format(12)
.match(/€.*12/)
class CurrencyInput extends Component {
state = {
value: ''
}
static getDerivedStateFromProps(nextProps) {
return {
value: nextProps.value
}
}
getSnapshotBeforeUpdate = () => {
return this.input.selectionStart
}
componentDidMount() {
this.adaptInputSize()
}
adaptInputSize = () => {
if (this.input && isCurrencyPrefixed(this.props.language))
this.input.style.width = this.input.value.length + 0.2 + 'ch'
}
componentDidUpdate = (_, __, cursorPosition) => {
this.input.selectionStart = cursorPosition
this.input.selectionEnd = cursorPosition
this.adaptInputSize()
}
focusInput = () => {
this.input.focus()
}
handleChange = event => {
let value = event.target.value
value = value
.replace(/,/g, '.')
.replace(/[^\d.]/g, '')
.replace(/\.(.*)\.(.*)/g, '$1.$2')
this.setState({ value }, this.adaptInputSize)
if (value.endsWith('.')) {
return
}
if (this.props.onChange) {
event.target.value = value
this.props.onChange(event)
}
}
render() {
let forwardedProps = dissoc(
['onChange', 'value', 'language', 'className'],
this.props
)
return (
<div
onClick={this.focusInput}
className={classnames(
this.props.className,
'currencyInput__container'
)}>
{isCurrencyPrefixed(this.props.language) && '€'}
<input
{...forwardedProps}
className="currencyInput__input"
onChange={this.handleChange}
ref={ref => (this.input = ref)}
value={this.state.value}
/>
{!isCurrencyPrefixed(this.props.language) && <>&nbsp;</>}
</div>
)
}
}
export default CurrencyInput

View File

@ -0,0 +1,68 @@
import CurrencyInput from './CurrencyInput'
import React from 'react'
import { shallow } from 'enzyme'
import { expect } from 'chai'
import { spy, match } from 'sinon'
let getInput = component => shallow(component).find('input')
describe('CurrencyInput', () => {
it('should render an input', () => {
expect(getInput(<CurrencyInput />)).to.have.length(1)
})
it('should accept both . and , as decimal separator', () => {
let onChange = spy()
const input = getInput(<CurrencyInput value={0} onChange={onChange} />)
input.simulate('change', { target: { value: '12.1' } })
expect(onChange).to.have.been.calledWith(
match.hasNested('target.value', '12.1')
)
input.simulate('change', { target: { value: '12,1' } })
expect(onChange).to.have.been.calledWith(
match.hasNested('target.value', '12.1')
)
})
it('should not accept negative number', () => {
let onChange = spy()
const input = getInput(<CurrencyInput value={0} onChange={onChange} />)
input.simulate('change', { target: { value: '-12' } })
expect(onChange).to.have.been.calledWith(
match.hasNested('target.value', '12')
)
})
it('should not accept anything else than number', () => {
let onChange = spy()
const input = getInput(<CurrencyInput value={0} onChange={onChange} />)
input.simulate('change', { target: { value: '*1/2abc3' } })
expect(onChange).to.have.been.calledWith(
match.hasNested('target.value', '123')
)
})
it('should pass other props to the input', () => {
const input = getInput(<CurrencyInput autoFocus />)
expect(input.prop('autoFocus')).to.be.true
})
it('should not call onChange while the decimal part is being written', () => {
let onChange = spy()
const input = getInput(<CurrencyInput value={0} onChange={onChange} />)
input.simulate('change', { target: { value: '111,' } })
expect(onChange).not.to.have.been.called
})
it('should change the position of the currency symbol depending on the language', () => {
const inputFr = shallow(<CurrencyInput value={0} language="fr" />)
expect(
inputFr
.children()
.last()
.text()
).to.includes('€')
const inputEn = shallow(<CurrencyInput value={0} language="en" />)
expect(
inputEn
.children()
.first()
.text()
).to.includes('€')
})
})

View File

@ -1,6 +1,4 @@
import {
curry,
evolve,
path,
propEq,
pathEq,
@ -14,20 +12,19 @@ import {
pathOr,
toPairs,
keys,
head,
find
} from 'ramda'
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'
import { Link } from 'react-router-dom'
import withLanguage from './withLanguage'
import { formValueSelector } from 'redux-form'
import './Results.css'
import '../engine/mecanismViews/Somme.css'
import { capitalise0, humanFigure } from '../utils'
import { capitalise0 } from '../utils'
import { nameLeaf, encodeRuleName, findRuleByDottedName } from 'Engine/rules'
// Filtered variables and rules can't be filtered in a uniform way, for now
@ -70,6 +67,12 @@ export let byBranch = analysis => {
return result
}
let formatCurrency = language => number =>
Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR'
}).format(number)
@withRouter
@connect(state => ({
analysis: state.analysis,
@ -79,6 +82,7 @@ export let byBranch = analysis => {
inversions: formValueSelector('conversation')(state, 'inversions')
}))
@translate()
@withLanguage
export default class ResultsGrid extends Component {
render() {
let {
@ -86,7 +90,8 @@ export default class ResultsGrid extends Component {
situationGate,
targetNames,
inversions,
flatRules
flatRules,
language
} = this.props,
rules = flatRules
@ -123,6 +128,7 @@ export default class ResultsGrid extends Component {
flatRules,
'contrat salarié . avantages salarié'
)
let formatLocalizedCurrency = formatCurrency(language)
return (
<div className="somme resultsGrid">
@ -140,7 +146,7 @@ export default class ResultsGrid extends Component {
colSpan={(relevantSalaries.size - 1) * 2}
className="element value"
id="sommeBase">
{humanFigure(2)(base)}{' '}
{formatLocalizedCurrency(base)}{' '}
</td>
</tr>
</thead>
@ -158,7 +164,7 @@ export default class ResultsGrid extends Component {
className="element value"
id="sommeBase">
<span className="operator">+ </span>
{humanFigure(2)(avan)}{' '}
{formatLocalizedCurrency(avan)}{' '}
</td>
</tr>
</thead>
@ -176,21 +182,20 @@ export default class ResultsGrid extends Component {
className="element value"
id="sommeBase">
<span className="operator">= </span>
{humanFigure(2)(brut)}{' '}
{formatLocalizedCurrency(brut)}{' '}
</td>
</tr>
</thead>
<tbody>
{toPairs(results).map(([branch, values]) => {
let props = {
key: branch,
branch,
values,
analysis,
rules,
relevantSalaries
}
return <Row {...props} />
return <Row key={branch} {...props} />
})}
<ReductionRow
node={fromDict('contrat salarié . réductions de cotisations')}
@ -204,7 +209,7 @@ export default class ResultsGrid extends Component {
=
</td>
<td key="net" className="element value">
{humanFigure(2)(net)}{' '}
{formatLocalizedCurrency(net)}{' '}
<span className="annotation">
<Trans>Salaire net</Trans>
</span>
@ -216,7 +221,7 @@ export default class ResultsGrid extends Component {
=
</td>,
<td key="total" className="element value">
{humanFigure(2)(total)}{' '}
{formatLocalizedCurrency(total)}{' '}
<span className="annotation">
<Trans>Salaire chargé</Trans>
</span>
@ -231,25 +236,31 @@ export default class ResultsGrid extends Component {
}
@translate()
@withLanguage
class Row extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
state = {
folded: true
}
render() {
let { rules, branch, values, analysis, relevantSalaries } = this.props,
let {
rules,
branch,
values,
analysis,
relevantSalaries,
language,
t
} = this.props,
detail = byName(values),
ruleData = mapObjIndexed(
(v, k, o) => findRuleByDottedName(rules, k),
(v, k) => findRuleByDottedName(rules, k),
detail
),
{ i18n } = this.context
formatLocalizedCurrency = formatCurrency(language)
let title = name => {
let node = ruleData[name]
return node.title || capitalise0(i18n.t(node.name))
return node.title || capitalise0(t(node.name))
}
let aggregateRow = (
@ -257,8 +268,8 @@ class Row extends Component {
key="aggregateRow"
onClick={() => this.setState({ folded: !this.state.folded })}>
<td key="category" className="element category name">
{capitalise0(i18n.t(branch))}&nbsp;<span className="unfoldIndication">
{this.state.folded ? i18n.t('déplier') + ' >' : i18n.t('replier')}
{capitalise0(t(branch))}&nbsp;<span className="unfoldIndication">
{this.state.folded ? t('déplier') + ' >' : t('replier')}
</span>
</td>
{this.state.folded ? (
@ -269,7 +280,7 @@ class Row extends Component {
-
</td>
<td key="value1" className="element value">
{humanFigure(2)(cell(branch, 'salarié', analysis))}
{formatLocalizedCurrency(cell(branch, 'salarié', analysis))}
</td>
</>
)}
@ -279,7 +290,7 @@ class Row extends Component {
+
</td>
<td key="value2" className="element value">
{humanFigure(2)(cell(branch, 'employeur', analysis))}
{formatLocalizedCurrency(cell(branch, 'employeur', analysis))}
</td>
</>
)}
@ -305,7 +316,9 @@ class Row extends Component {
-
</td>
<td key="value1" className="element value">
{humanFigure(2)(subCell(detail, subCellName, 'salarié'))}
{formatLocalizedCurrency(
subCell(detail, subCellName, 'salarié')
)}
</td>
</>
)}
@ -315,7 +328,9 @@ class Row extends Component {
+
</td>
<td key="value2" className="element value">
{humanFigure(2)(subCell(detail, subCellName, 'employeur'))}
{formatLocalizedCurrency(
subCell(detail, subCellName, 'employeur')
)}
</td>
</>
)}
@ -330,25 +345,23 @@ class Row extends Component {
// TODO Ce code est beaucoup trop spécifique
// C'est essentiellement une copie de Row
@translate()
@withLanguage
class ReductionRow extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
state = {
folded: true
}
render() {
let { relevantSalaries, node } = this.props,
{ i18n } = this.context
let { relevantSalaries, node, language, t } = this.props
if (!relevantSalaries.has('salaire total')) return null
let value = node && node.nodeValue ? node.nodeValue : 0
let formatLocalizedCurrency = formatCurrency(language)
let aggregateRow = (
<tr
key="aggregateRowReductions"
onClick={() => this.setState({ folded: !this.state.folded })}>
<td key="category" className="element category name">
<Trans>Réductions</Trans>&nbsp;<span className="unfoldIndication">
{this.state.folded ? i18n.t('déplier') + ' >' : i18n.t('replier')}
{this.state.folded ? t('déplier') + ' >' : t('replier')}
</span>
</td>
{this.state.folded ? (
@ -359,7 +372,7 @@ class ReductionRow extends Component {
+
</td>
<td key="value1" className="element value">
{humanFigure(2)(0)}
{formatLocalizedCurrency(0)}
</td>
</>
)}
@ -369,7 +382,7 @@ class ReductionRow extends Component {
-
</td>
<td key="value2" className="element value">
{humanFigure(2)(value)}
{formatLocalizedCurrency(value)}
</td>
</>
)}
@ -393,7 +406,7 @@ class ReductionRow extends Component {
+
</td>
<td key="value1" className="element value">
{humanFigure(2)(0)}
{formatLocalizedCurrency(0)}
</td>
</>
)}
@ -403,7 +416,7 @@ class ReductionRow extends Component {
-
</td>
<td key="value2" className="element value">
{humanFigure(2)(value)}
{formatLocalizedCurrency(value)}
</td>
</>
)}

View File

@ -5,7 +5,7 @@ import ReactPiwik from './Tracker'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import Smiley from './SatisfactionSmiley'
import TypeFormEmbed from './TypeFormEmbed'
import PropTypes from 'prop-types'
import withLanguage from './withLanguage'
import { Trans, translate } from 'react-i18next'
@connect(state => ({
@ -15,15 +15,13 @@ import { Trans, translate } from 'react-i18next'
conversationStarted: state.conversationStarted
}))
@translate()
@withLanguage
export default class Sondage extends Component {
state = {
visible: false,
showForm: false,
askFeedbackTime: 'AFTER_FIRST_ESTIMATE'
}
static contextTypes = {
i18n: PropTypes.object.isRequired
}
static getDerivedStateFromProps(nextProps, currentState) {
let feedbackAlreadyAsked = !!document.cookie.includes('feedback_asked=true')
let conditions = {
@ -57,7 +55,8 @@ export default class Sondage extends Component {
this.setCookie()
}
render() {
let { satisfaction, showForm, visible, askFeedbackTime } = this.state
let { satisfaction, showForm, visible, askFeedbackTime } = this.state,
{ language } = this.props
return (
<>
@ -67,7 +66,7 @@ export default class Sondage extends Component {
exterieur: false,
satisfaction,
answertiming: askFeedbackTime,
language: this.context.i18n.language
language
}}
/>
)}

View File

@ -80,23 +80,25 @@
#targetSelection .targetInputOrValue {
font-size: 125%;
display: flex;
height: 1.6em;
justify-content: flex-end;
margin-left: .6em;
align-items: flex-end;
margin-left: 0.6em;
}
#targetSelection .editable {
border-bottom: 1px dashed #ffffff91;
min-width: 2.5em;
height: inherit;
display: inline-block;
}
#targetSelection .attractClick,
#targetSelection input[type='number'] {
width: 5em !important;
#targetSelection .targetInput {
width: 5.5em !important;
display: inline-block;
height: 1.6em;
text-align: right;
font-weight: 600;
font-weight: 700;
padding: 0;
padding: 0em 0.5em;
background: white;

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import formValueTypes from 'Components/conversation/formValueTypes'
import { findRuleByName } from 'Engine/rules'
import { propEq, curry } from 'ramda'
import './TargetSelection.css'
@ -11,8 +10,9 @@ import { connect } from 'react-redux'
import { RuleValue } from './rule/RuleValueVignette'
import classNames from 'classnames'
import ProgressCircle from './ProgressCircle/ProgressCircle'
import withLanguage from './withLanguage'
import InputSuggestions from 'Components/conversation/InputSuggestions'
import { buildValidationFunction } from './conversation/FormDecorator'
import CurrencyInput from './CurrencyInput/CurrencyInput'
export let salaries = ['salaire total', 'salaire de base', 'salaire net']
export let popularTargetNames = [...salaries, 'aides employeur']
@ -65,7 +65,9 @@ export default class TargetSelection extends Component {
<Trans>Estimation approximative</Trans>
</b>{' '}
<br />
<Trans i18nKey="defaults">pour un CDI non cadre</Trans>
<Trans i18nKey="defaults">
pour une situation par défaut (CDI non cadre).
</Trans>
</p>
<BlueButton onClick={this.props.startConversation}>
<Trans>Affiner le calcul</Trans>
@ -143,37 +145,32 @@ let Header = ({ target, conversationStarted, isActiveInput }) => {
)
}
let validate = buildValidationFunction(formValueTypes['euros'])
let InputComponent = ({ input, meta: { dirty, error } }) => (
<span>
{dirty && error && <span className="input-error">{error}</span>}
<input type="number" {...input} autoFocus />
</span>
)
let TargetInputOrValue = ({
target,
targets,
firstEstimationComplete,
activeInput,
setActiveInput
}) => (
<span className="targetInputOrValue">
{activeInput === target.dottedName ? (
<Field
name={target.dottedName}
component={InputComponent}
type="text"
validate={validate}
/>
) : (
<TargetValue {...{ targets, target, activeInput, setActiveInput }} />
)}
{(firstEstimationComplete || target.question) && (
<span className="unit"></span>
)}
</span>
)
let CurrencyField = props => {
return (
<CurrencyInput
className="targetInput"
autoFocus
{...props.input}
{...props}
/>
)
}
let TargetInputOrValue = withLanguage(
({ target, targets, activeInput, setActiveInput, language }) => (
<span className="targetInputOrValue">
{activeInput === target.dottedName ? (
<Field
name={target.dottedName}
component={CurrencyField}
language={language}
/>
) : (
<TargetValue {...{ targets, target, activeInput, setActiveInput }} />
)}
</span>
)
)
@connect(
() => ({}),
dispatch => ({
@ -190,9 +187,7 @@ class TargetValue extends Component {
setActiveInput
} = this.props,
targetWithValue = targets.find(propEq('dottedName', target.dottedName)),
value = targetWithValue && targetWithValue.nodeValue,
humanValue = value != null && value.toFixed(0)
value = targetWithValue && targetWithValue.nodeValue
return (
<span
className={classNames({
@ -202,10 +197,9 @@ class TargetValue extends Component {
onClick={() => {
if (!target.question) return
if (value != null) {
setFormValue(target.dottedName, humanValue + '')
setFormValue(target.dottedName, Math.floor(value) + '')
setFormValue(activeInput, '')
}
setActiveInput(target.dottedName)
}}>
<RuleValue value={value} />

View File

@ -1,19 +1,17 @@
import React, { Component } from 'react'
import { Trans, translate } from 'react-i18next'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { connect } from 'react-redux'
import { Field, change, formValueSelector } from 'redux-form'
import { Field, change } from 'redux-form'
import { stepAction } from '../../actions'
import { capitalise0 } from '../../utils'
import { path } from 'ramda'
import Explicable from 'Components/conversation/Explicable'
import IgnoreStepButton from './IgnoreStepButton'
import { findRuleByDottedName } from 'Engine/rules'
export let buildValidationFunction = valueType => {
let validator = valueType ? valueType.validator : {},
{ pre = v => v, test = v => true, error } = validator
{ pre = v => v, test = () => true, error } = validator
return v => v != undefined && (test(pre(v)) ? undefined : error)
}
/*
@ -39,10 +37,8 @@ export var FormDecorator = formType => RenderField =>
dispatch(change('conversation', field, value))
})
)
@translate()
class extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
state = {
helpVisible: false
}
@ -62,17 +58,16 @@ export var FormDecorator = formType => RenderField =>
renderUnfolded() {
let {
setFormValue,
stepAction,
subquestion,
possibleChoice, // should be found in the question set theoritically, but it is used for a single choice question -> the question itself is dynamic and cannot be input as code,
defaultValue,
valueType,
fieldName,
inversion,
themeColours
} = this.props,
{ i18n } = this.context
setFormValue,
stepAction,
subquestion,
possibleChoice, // should be found in the question set theoritically, but it is used for a single choice question -> the question itself is dynamic and cannot be input as code,
defaultValue,
valueType,
fieldName,
inversion,
themeColours
} = this.props
/* There won't be any answer zone here, widen the question zone */
let wideQuestion = formType == 'rhetorical-question' && !possibleChoice
@ -144,21 +139,19 @@ export var FormDecorator = formType => RenderField =>
dottedName,
fieldName,
fieldTitle,
flatRules
} = this.props,
{ i18n } = this.context
flatRules,
t
} = this.props
let answer = situationGate(fieldName),
rule = findRuleByDottedName(flatRules, dottedName + ' . ' + answer),
translatedAnswer = (rule && rule.title) || i18n.t(answer)
translatedAnswer = (rule && rule.title) || t(answer)
return (
<div className="foldedQuestion">
<span className="borderWrapper">
<span className="title">{capitalise0(fieldTitle || title)}</span>
<span className="answer">
{translatedAnswer}
</span>
<span className="answer">{translatedAnswer}</span>
</span>
<button
className="edit"
@ -170,7 +163,6 @@ export var FormDecorator = formType => RenderField =>
<Trans>Modifier</Trans>
</span>
</button>
{}
</div>
)
}

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import PropTypes from 'prop-types'
import { FormDecorator } from './FormDecorator'
import classnames from 'classnames'
import SendButton from './SendButton'
@ -9,9 +8,6 @@ import InputSuggestions from './InputSuggestions'
@FormDecorator('input')
@translate()
export default class Input extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
render() {
let {
input,
@ -19,13 +15,13 @@ export default class Input extends Component {
submit,
valueType,
meta: { dirty, error, active },
t,
themeColours
} = this.props,
answerSuffix = valueType.suffix,
suffixed = answerSuffix != null,
inputError = dirty && error,
submitDisabled = !dirty || inputError,
{ i18n } = this.context
submitDisabled = !dirty || inputError
return (
<span>
@ -39,7 +35,7 @@ export default class Input extends Component {
className={classnames({ suffixed })}
id={'step-' + dottedName}
inputMode="numeric"
placeholder={i18n.t('votre réponse')}
placeholder={t('votre réponse')}
style={
!active
? { border: '2px dashed #ddd' }

View File

@ -3,24 +3,20 @@ import './InputSuggestions.css'
import withColours from '../withColours'
import { toPairs } from 'ramda'
import { translate } from 'react-i18next'
import PropTypes from 'prop-types'
@withColours
@translate()
export default class extends Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
export default class InputSuggestions extends Component {
state = { suggestion: null }
render() {
let {
suggestions,
onSecondClick,
onFirstClick,
colouredBackground,
colours
} = this.props,
{ i18n } = this.context
suggestions,
onSecondClick,
onFirstClick,
colouredBackground,
colours,
t
} = this.props
if (!suggestions) return null
return (
@ -41,7 +37,7 @@ export default class extends Component {
? colours.textColour
: colours.textColourOnWhite
}}>
<span title={i18n.t('cliquez pour insérer cette suggestion')}>
<span title={t('cliquez pour insérer cette suggestion')}>
{text}
</span>
</li>

View File

@ -26,7 +26,10 @@ let getOptions = input =>
@FormDecorator('select')
export default class Select extends Component {
render() {
let { input: { onChange }, submit, suggestions } = this.props,
let {
input: { onChange },
submit
} = this.props,
submitOnChange = option => {
onChange(option.code)
submit()

View File

@ -6,30 +6,33 @@ import { AttachDictionary } from '../AttachDictionary'
import knownMecanisms from 'Engine/known-mecanisms.yaml'
import { makeJsx } from 'Engine/evaluation'
import './Algorithm.css'
import { humanFigure } from '../../utils'
import { head } from 'ramda'
import { analyse } from 'Engine/traverse'
import { exampleSituationGateWithDefaults } from './Examples'
import PropTypes from 'prop-types'
import withLanguage from '../withLanguage'
let RuleWithoutFormula = () => (
<p>
<Trans i18nKey="input">
Cette règle n'a pas de formule de calcul pour l'instant. Sa valeur doit donc
être renseignée directement.
Cette règle na pas de formule de calcul pour linstant. Sa valeur doit
donc être renseignée directement.
</Trans>
</p>
)
@AttachDictionary(knownMecanisms)
@translate()
@withLanguage
export default class Algorithm extends React.Component {
static contextTypes = {
i18n: PropTypes.object.isRequired
}
render() {
let { rule: displayedRule, showValues, currentExample, rules } = this.props,
{ i18n } = this.context,
let {
rule: displayedRule,
showValues,
currentExample,
t,
rules,
language
} = this.props,
ruleWithoutFormula =
!displayedRule['formule'] ||
path(['formule', 'explanation', 'une possibilité'], displayedRule)
@ -52,7 +55,9 @@ export default class Algorithm extends React.Component {
)
applicabilityMecanisms.length > 0 && (
<section id="declenchement">
<h2><Trans>Déclenchement</Trans></h2>
<h2>
<Trans>Déclenchement</Trans>
</h2>
<ul>
{applicabilityMecanisms.map(v => (
<li key={v.name}>{makeJsx(v)}</li>
@ -65,7 +70,11 @@ export default class Algorithm extends React.Component {
<h2>
<Trans>Calcul</Trans>
{!ruleWithoutFormula && (
<small><Trans i18nKey="understand">Cliquez sur chaque chaque valeur pour comprendre</Trans></small>
<small>
<Trans i18nKey="understand">
Cliquez sur chaque chaque valeur pour comprendre
</Trans>
</small>
)}
</h2>
{ruleWithoutFormula ? (
@ -76,14 +85,16 @@ export default class Algorithm extends React.Component {
</section>
<section
id="ruleValue"
style={{ visibility: showValues ? 'visible' : 'hidden' }}
>
style={{ visibility: showValues ? 'visible' : 'hidden' }}>
<i className="fa fa-calculator" aria-hidden="true" />{' '}
{rule.nodeValue == 0
? i18n.t('Règle non applicable')
? t('Règle non applicable')
: rule.nodeValue == null
? i18n.t('Situation incomplète')
: humanFigure(2)(rule.nodeValue) + ' €'}
? t('Situation incomplète')
: Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR'
}).format(rule.nodeValue)}
</section>
</section>
</div>

View File

@ -1,14 +1,12 @@
import React from 'react'
import { Trans } from 'react-i18next'
import React, { Component } from 'react'
import withLanguage from '../withLanguage'
import { Link } from 'react-router-dom'
import { encodeRuleName } from 'Engine/rules'
import classNames from 'classnames'
import { humanFigure } from '../../utils'
import './RuleValueVignette.css'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
export default ({ name, type, title, nodeValue: ruleValue }) => (
let RuleValueVignette = ({ name, title, nodeValue: ruleValue }) => (
<span key={name} className="RuleValueVignette">
<Link to={'/règle/' + encodeRuleName(name)}>
<div className="rule-box">
@ -19,20 +17,37 @@ export default ({ name, type, title, nodeValue: ruleValue }) => (
</span>
)
export let RuleValue = ({ value }) =>
do {
@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 ? ['unsatisfied', ''] : ['figure', humanFigure(0)(value)]
;<ReactCSSTransitionGroup
transitionName="flash"
transitionEnterTimeout={100}
transitionLeaveTimeout={100}>
<span key={text} className="Rule-value">
{' '}
<span className={className}>{text}</span>
</span>
</ReactCSSTransitionGroup>
: unsatisfied
? ['unsatisfied', '']
: [
'figure',
Intl.NumberFormat(language, {
style: 'currency',
currency: 'EUR',
maximumFractionDigits: 0,
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>
)
}
}
export default RuleValueVignette

View File

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

View File

@ -6,9 +6,3 @@ export let getIframeOption = optionName => {
hasOption = url.includes(optionName + '=')
return hasOption && url.split(optionName + '=')[1].split('&')[0]
}
export let fmt =
'Intl' in window ? new Intl.NumberFormat('fr-FR').format : v => v
export let humanFigure = decimalDigits => value =>
fmt(value.toFixed(decimalDigits))

View File

@ -52,7 +52,7 @@ module.exports = {
},
{
test: /\.js$/,
exclude: /node_modules|dist|test/,
exclude: /node_modules|dist/,
loader: 'babel-loader'
},
{

View File

@ -1,6 +1,4 @@
var webpack = require('webpack'),
path = require('path'),
HardSourceWebpackPlugin = require('hard-source-webpack-plugin'),
common = require('./webpack.common.js')
module.exports = {

304
yarn.lock
View File

@ -663,6 +663,16 @@
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
resolved "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
dependencies:
samsam "1.3.0"
"@types/node@*":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.0.3.tgz#1f89840c7aac2406cc43a2ecad98fc02a8e130e4"
JSONStream@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea"
@ -1898,6 +1908,10 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
boom@4.x.x:
version "4.3.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
@ -2228,6 +2242,17 @@ check-error@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
cheerio@^1.0.0-rc.2:
version "1.0.0-rc.2"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
dependencies:
css-select "~1.2.0"
dom-serializer "~0.1.0"
entities "~1.1.1"
htmlparser2 "^3.9.1"
lodash "^4.15.0"
parse5 "^3.0.1"
chokidar@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@ -2767,6 +2792,15 @@ css-loader@^0.28.11:
postcss-value-parser "^3.3.0"
source-list-map "^2.0.0"
css-select@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
dependencies:
boolbase "~1.0.0"
css-what "2.1"
domutils "1.5.1"
nth-check "~1.0.1"
css-selector-tokenizer@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
@ -2775,6 +2809,10 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
css-what@2.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
cssesc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
@ -3080,7 +3118,7 @@ dezalgo@^1.0.0, dezalgo@~1.0.3:
asap "^2.0.0"
wrappy "1"
diff@3.5.0, diff@^3.3.1, diff@^3.5.0:
diff@3.5.0, diff@^3.1.0, diff@^3.3.1, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@ -3130,6 +3168,13 @@ doctrine@^2.0.2, doctrine@^2.1.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
dom-serializer@0, dom-serializer@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
dependencies:
domelementtype "~1.1.1"
entities "~1.1.1"
dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
@ -3138,12 +3183,40 @@ domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
domelementtype@1, domelementtype@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
domelementtype@~1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
domexception@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
dependencies:
webidl-conversions "^4.0.2"
domhandler@^2.3.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259"
dependencies:
domelementtype "1"
domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
dependencies:
dom-serializer "0"
domelementtype "1"
domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
dependencies:
dom-serializer "0"
domelementtype "1"
dot-prop@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
@ -3169,8 +3242,8 @@ duplexer@~0.1.1:
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
duplexify@^3.4.2, duplexify@^3.5.3:
version "3.5.4"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4"
version "3.6.0"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410"
dependencies:
end-of-stream "^1.0.0"
inherits "^2.0.1"
@ -3247,10 +3320,55 @@ enhanced-resolve@^4.0.0:
memory-fs "^0.4.0"
tapable "^1.0.0"
entities@^1.1.1, entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
envinfo@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-4.4.2.tgz#472c49f3a8b9bca73962641ce7cb692bf623cd1c"
enzyme-adapter-react-16@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4"
dependencies:
enzyme-adapter-utils "^1.3.0"
lodash "^4.17.4"
object.assign "^4.0.4"
object.values "^1.0.4"
prop-types "^15.6.0"
react-reconciler "^0.7.0"
react-test-renderer "^16.0.0-0"
enzyme-adapter-utils@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7"
dependencies:
lodash "^4.17.4"
object.assign "^4.0.4"
prop-types "^15.6.0"
enzyme@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479"
dependencies:
cheerio "^1.0.0-rc.2"
function.prototype.name "^1.0.3"
has "^1.0.1"
is-boolean-object "^1.0.0"
is-callable "^1.1.3"
is-number-object "^1.0.3"
is-string "^1.0.4"
is-subset "^0.1.1"
lodash "^4.17.4"
object-inspect "^1.5.0"
object-is "^1.0.1"
object.assign "^4.1.0"
object.entries "^1.0.4"
object.values "^1.0.4"
raf "^3.4.0"
rst-selector-parser "^2.2.3"
err-code@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
@ -3274,7 +3392,7 @@ error@^7.0.2:
string-template "~0.2.1"
xtend "~4.0.0"
es-abstract@^1.7.0:
es-abstract@^1.6.1, es-abstract@^1.7.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681"
dependencies:
@ -3950,10 +4068,18 @@ fstream@^1.0.0, fstream@^1.0.2:
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.0.2, function-bind@^1.1.1:
function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
function.prototype.name@^1.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
dependencies:
define-properties "^1.1.2"
function-bind "^1.1.1"
is-callable "^1.1.3"
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@ -4473,6 +4599,17 @@ html-parse-stringify2@2.0.1:
dependencies:
void-elements "^2.0.1"
htmlparser2@^3.9.1:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
dependencies:
domelementtype "^1.3.0"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
readable-stream "^2.0.2"
http-auth@3.1.x:
version "3.1.3"
resolved "https://registry.yarnpkg.com/http-auth/-/http-auth-3.1.3.tgz#945cfadd66521eaf8f7c84913d377d7b15f24e31"
@ -4719,6 +4856,14 @@ interpret@^1.0.0, interpret@^1.0.1, interpret@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
intl-locales-supported@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/intl-locales-supported/-/intl-locales-supported-1.0.0.tgz#9a9d94dbf104a87818881952dcb782053f0aeefa"
intl@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
into-stream@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
@ -4770,6 +4915,10 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
is-boolean-object@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -4905,6 +5054,10 @@ is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
is-number-object@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799"
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@ -5007,6 +5160,14 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
is-string@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
is-subset@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
is-svg@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
@ -5253,6 +5414,10 @@ jsx-ast-utils@^2.0.1:
dependencies:
array-includes "^3.0.3"
just-extend@^1.1.27:
version "1.1.27"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
@ -5507,10 +5672,18 @@ lodash.debounce@^4.0.4:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -5590,6 +5763,10 @@ loglevelnext@^1.0.1:
es6-symbol "^3.1.1"
object.assign "^4.1.0"
lolex@^2.2.0, lolex@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
@ -6053,7 +6230,7 @@ nearley-loader@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/nearley-loader/-/nearley-loader-2.0.0.tgz#8d75fd2ab3ca9f6153ae099b2bf18f8d5605d203"
nearley@^2.13.0:
nearley@^2.13.0, nearley@^2.7.10:
version "2.13.0"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.13.0.tgz#6e7b0f4e68bfc3e74c99eaef2eda39e513143439"
dependencies:
@ -6086,6 +6263,16 @@ nice-try@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
nise@^1.2.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.3.3.tgz#c17a850066a8a1dfeb37f921da02441afc4a82ba"
dependencies:
"@sinonjs/formatio" "^2.0.0"
just-extend "^1.1.27"
lolex "^2.3.2"
path-to-regexp "^1.7.0"
text-encoding "^0.6.4"
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
@ -6458,6 +6645,12 @@ npm@^5.7.1:
gauge "~2.7.3"
set-blocking "~2.0.0"
nth-check@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4"
dependencies:
boolbase "~1.0.0"
nu-stream@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nu-stream/-/nu-stream-3.3.1.tgz#d9ab5037bfd00c86e76bbe28686727063b91b3af"
@ -6490,6 +6683,14 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-inspect@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
object-is@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
object-keys@^1.0.11, object-keys@^1.0.8:
version "1.0.11"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
@ -6509,6 +6710,15 @@ object.assign@^4.0.4, object.assign@^4.1.0:
has-symbols "^1.0.0"
object-keys "^1.0.11"
object.entries@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f"
dependencies:
define-properties "^1.1.2"
es-abstract "^1.6.1"
function-bind "^1.1.0"
has "^1.0.1"
object.omit@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
@ -6522,6 +6732,15 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
object.values@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a"
dependencies:
define-properties "^1.1.2"
es-abstract "^1.6.1"
function-bind "^1.1.0"
has "^1.0.1"
obuf@^1.0.0, obuf@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
@ -6781,6 +7000,12 @@ parse5@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
parse5@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
dependencies:
"@types/node" "*"
parseurl@~1.3.1, parseurl@~1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
@ -7360,10 +7585,14 @@ qrcode-terminal@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz#ffc6c28a2fc0bfb47052b47e23f4f446a5fbdb9e"
qs@6.5.1, qs@~6.5.1:
qs@6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
qs@~6.5.1:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
query-string@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@ -7399,6 +7628,12 @@ qw@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
raf@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
dependencies:
performance-now "^2.1.0"
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
@ -7551,6 +7786,10 @@ react-input-autosize@^2.1.2:
dependencies:
prop-types "^15.5.8"
react-is@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22"
react-json-tree@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-json-tree/-/react-json-tree-0.11.0.tgz#f5b17e83329a9c76ae38be5c04fda3a7fd684a35"
@ -7567,6 +7806,15 @@ react-pure-render@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-pure-render/-/react-pure-render-1.0.2.tgz#9d8a928c7f2c37513c2d064e57b3e3c356e9fabb"
react-reconciler@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
react-redux@^5.0.7:
version "5.0.7"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
@ -7629,6 +7877,15 @@ react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"
react-test-renderer@^16.0.0-0:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a"
dependencies:
fbjs "^0.8.16"
object-assign "^4.1.1"
prop-types "^15.6.0"
react-is "^16.3.2"
react-transition-group@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
@ -8239,6 +8496,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rst-selector-parser@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
dependencies:
lodash.flattendeep "^4.4.0"
nearley "^2.7.10"
run-async@^2.0.0, run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@ -8289,6 +8553,10 @@ safer-buffer@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
samsam@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
sanctuary-type-classes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/sanctuary-type-classes/-/sanctuary-type-classes-3.0.0.tgz#e4cf422abceb8c78b6f625d84d61fdb5c6d44116"
@ -8463,6 +8731,22 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
sinon-chai@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.0.0.tgz#d5cbd70fa71031edd96b528e0eed4038fcc99f29"
sinon@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.5.0.tgz#427ae312a337d3c516804ce2754e8c0d5028cb04"
dependencies:
"@sinonjs/formatio" "^2.0.0"
diff "^3.1.0"
lodash.get "^4.4.2"
lolex "^2.2.0"
nise "^1.2.0"
supports-color "^5.1.0"
type-detect "^4.0.5"
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@ -8962,6 +9246,10 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
text-table@^0.2.0, text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -9083,7 +9371,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@^4.0.0:
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"