Merge pull request #523 from betagouv/améliorations-diverses
Petite refacto de sélecteurs et ravalement du retour utilisateurpull/478/head
commit
e86b7c1f43
|
@ -1,8 +1,23 @@
|
|||
.feedback-page {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
padding-top: 0.6rem;
|
||||
padding-bottom: 0.6rem;
|
||||
background: var(--lighterColour);
|
||||
border-radius: 0.9rem;
|
||||
padding: 0.6em 1em;
|
||||
}
|
||||
.feedback-page.stickToFooter {
|
||||
margin-bottom: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.feedback-page button.link-button {
|
||||
margin: 0 0.6em;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.feedback-page .feedbackButtons {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { Component } from 'react'
|
|||
import { Trans } from 'react-i18next'
|
||||
import type { Tracker } from 'Components/utils/withTracker'
|
||||
|
||||
type Props = { onEnd: () => void, tracker: Tracker }
|
||||
type Props = { onEnd: () => void, tracker: Tracker, onCancel: () => void }
|
||||
|
||||
class FeedbackForm extends Component<Props> {
|
||||
formRef: ?HTMLFormElement
|
||||
|
@ -23,9 +23,6 @@ class FeedbackForm extends Component<Props> {
|
|||
// $FlowFixMe
|
||||
body: new FormData(this.formRef)
|
||||
})
|
||||
this.handleClose()
|
||||
}
|
||||
handleClose = () => {
|
||||
this.props.onEnd()
|
||||
}
|
||||
|
||||
|
@ -34,7 +31,7 @@ class FeedbackForm extends Component<Props> {
|
|||
<ScrollToElement onlyIfNotVisible>
|
||||
<div style={{ textAlign: 'end' }}>
|
||||
<button
|
||||
onClick={this.handleClose}
|
||||
onClick={() => this.props.onCancel()}
|
||||
className="ui__ link-button"
|
||||
style={{ textDecoration: 'none', marginLeft: '0.3rem' }}
|
||||
aria-label="close">
|
||||
|
|
|
@ -11,11 +11,13 @@ import Form from './FeedbackForm'
|
|||
import type { Tracker } from 'Components/utils/withTracker'
|
||||
import type { Location } from 'react-router-dom'
|
||||
import type { Node } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type OwnProps = {
|
||||
blacklist: Array<string>,
|
||||
customMessage?: Node,
|
||||
customEventName?: string
|
||||
customEventName?: string,
|
||||
stickToFooter: boolean
|
||||
}
|
||||
type Props = OwnProps & {
|
||||
location: Location,
|
||||
|
@ -89,6 +91,7 @@ class PageFeedback extends Component<Props, State> {
|
|||
this.setState({ showForm: true })
|
||||
}
|
||||
render() {
|
||||
let { stickToFooter = false } = this.props
|
||||
if (this.feedbackAlreadyGiven) {
|
||||
return null
|
||||
}
|
||||
|
@ -96,56 +99,63 @@ class PageFeedback extends Component<Props, State> {
|
|||
this.props.location.pathname === '/' ? '' : this.props.location.pathname
|
||||
return (
|
||||
!this.props.blacklist.includes(pathname) && (
|
||||
<div className="feedback-page ui__ container notice">
|
||||
{!this.state.showForm && !this.state.showThanks && (
|
||||
<>
|
||||
<div>
|
||||
{this.props.customMessage || (
|
||||
<Trans i18nKey="feedback.question">
|
||||
Cette page vous est utile ?
|
||||
</Trans>
|
||||
)}{' '}
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
<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 style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<div
|
||||
className={classNames('feedback-page', 'ui__', 'notice', {
|
||||
stickToFooter
|
||||
})}>
|
||||
{!this.state.showForm && !this.state.showThanks && (
|
||||
<>
|
||||
<div>
|
||||
{this.props.customMessage || (
|
||||
<Trans i18nKey="feedback.question">
|
||||
Cette page vous est utile ?
|
||||
</Trans>
|
||||
)}{' '}
|
||||
<div className="feedbackButtons">
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => this.handleFeedback({ useful: true })}>
|
||||
<Trans>Oui</Trans>
|
||||
</button>{' '}
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => this.handleFeedback({ useful: false })}>
|
||||
<Trans>Non</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={this.handleErrorReporting}>
|
||||
<Trans i18nKey="feedback.reportError">
|
||||
Faire une suggestion
|
||||
</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
style={{ textAlign: 'right' }}
|
||||
className="ui__ link-button"
|
||||
onClick={this.handleErrorReporting}>
|
||||
<Trans i18nKey="feedback.reportError">
|
||||
Faire une suggestion
|
||||
</>
|
||||
)}
|
||||
{this.state.showThanks && (
|
||||
<div>
|
||||
<Trans i18nKey="feedback.thanks">
|
||||
Merci pour votre retour ! Vous pouvez nous contacter
|
||||
directement à{' '}
|
||||
<a href="mailto:contact@embauche.beta.gouv.fr">
|
||||
contact@embauche.beta.gouv.fr
|
||||
</a>
|
||||
</Trans>
|
||||
</button>{' '}
|
||||
</>
|
||||
)}
|
||||
{this.state.showThanks && (
|
||||
<div>
|
||||
<Trans i18nKey="feedback.thanks">
|
||||
Merci pour votre retour ! Vous pouvez nous contacter directement
|
||||
à{' '}
|
||||
<a href="mailto:contact@embauche.beta.gouv.fr">
|
||||
contact@embauche.beta.gouv.fr
|
||||
</a>
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{this.state.showForm && (
|
||||
<Form
|
||||
onEnd={() => this.setState({ showThanks: true, showForm: false })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{this.state.showForm && (
|
||||
<Form
|
||||
onEnd={() =>
|
||||
this.setState({ showThanks: false, showForm: false })
|
||||
}
|
||||
onCancel={() =>
|
||||
this.setState({ showThanks: false, showForm: false })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Trans } from 'react-i18next'
|
|||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router'
|
||||
import { animated, Spring } from 'react-spring'
|
||||
import { noUserInputSelector } from 'Selectors/analyseSelectors'
|
||||
import type { Location } from 'react-router'
|
||||
|
||||
type OwnProps = {
|
||||
|
@ -16,17 +15,10 @@ type OwnProps = {
|
|||
type Props = OwnProps & {
|
||||
startConversation: (?string) => void,
|
||||
location: Location,
|
||||
userInput: boolean,
|
||||
conversationStarted: boolean
|
||||
show: boolean
|
||||
}
|
||||
|
||||
const QuickLinks = ({
|
||||
startConversation,
|
||||
userInput,
|
||||
quickLinks,
|
||||
conversationStarted
|
||||
}: Props) => {
|
||||
const show = userInput && !conversationStarted
|
||||
const QuickLinks = ({ startConversation, show, quickLinks }: Props) => {
|
||||
return (
|
||||
<Spring
|
||||
to={{
|
||||
|
@ -45,7 +37,7 @@ const QuickLinks = ({
|
|||
flexWrap: 'wrap-reverse',
|
||||
fontSize: '110%',
|
||||
justifyContent: 'space-evenly',
|
||||
marginBottom: '0.6rem'
|
||||
marginBottom: '1rem'
|
||||
}}>
|
||||
{toPairs(quickLinks).map(([label, dottedName]) => (
|
||||
<button
|
||||
|
@ -67,8 +59,6 @@ export default (compose(
|
|||
connect(
|
||||
(state, props) => ({
|
||||
key: props.language,
|
||||
userInput: !noUserInputSelector(state),
|
||||
conversationStarted: state.conversationStarted,
|
||||
quickLinks: state.simulation?.config["questions à l'affiche"]
|
||||
}),
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { startConversation } from 'Actions/actions'
|
||||
import withTracker from 'Components/utils/withTracker'
|
||||
import { compose } from 'ramda'
|
||||
import React, { Component } from 'react'
|
||||
import { React, T } from 'Components'
|
||||
import { connect } from 'react-redux'
|
||||
import { formValueSelector } from 'redux-form'
|
||||
import ficheDePaieSelectors from 'Selectors/ficheDePaieSelectors'
|
||||
|
@ -24,10 +24,18 @@ export default compose(
|
|||
}
|
||||
)
|
||||
)(
|
||||
class SalaryExplanation extends Component {
|
||||
class SalaryExplanation extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Animate.fromBottom delay={2000}>
|
||||
<p>
|
||||
<T k="simulateurs.salarié.description">
|
||||
Dès que le salarié est déclaré et payé, il est couvert par le
|
||||
régime général de la Sécurité sociale (santé, maternité,
|
||||
invalidité, vieillesse, maladie professionnelle et accidents) et
|
||||
chômage.
|
||||
</T>
|
||||
</p>
|
||||
{!this.props.conversationStarted ? (
|
||||
<SalaryFirstExplanation {...this.props} />
|
||||
) : (
|
||||
|
|
|
@ -45,8 +45,7 @@ export default compose(
|
|||
let arePreviousAnswers = previousAnswers.length > 0,
|
||||
displayConversation =
|
||||
!targetsTriggerConversation || conversationStarted,
|
||||
showTargets =
|
||||
targetsTriggerConversation || !noUserInput || showTargetsAnyway
|
||||
showTargets = targetsTriggerConversation || showTargetsAnyway
|
||||
return (
|
||||
<>
|
||||
{this.state.displayAnswers && (
|
||||
|
@ -101,9 +100,8 @@ export default compose(
|
|||
)}
|
||||
|
||||
{showTargets && this.props.targets}
|
||||
{!noUserInput && this.props.explanation}
|
||||
{!noUserInput && !noFeedback && (
|
||||
<div style={{ margin: '-0.6rem' }}>
|
||||
{!noFeedback && (
|
||||
<div>
|
||||
<PageFeedback
|
||||
customMessage={
|
||||
<T k="feedback.simulator">
|
||||
|
|
|
@ -14,14 +14,14 @@ import { Link } from 'react-router-dom'
|
|||
import { change, Field, formValueSelector, reduxForm } from 'redux-form'
|
||||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
flatRulesSelector,
|
||||
noUserInputSelector
|
||||
flatRulesSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import Animate from 'Ui/animate'
|
||||
import AnimatedTargetValue from 'Ui/AnimatedTargetValue'
|
||||
import CurrencyInput from './CurrencyInput/CurrencyInput'
|
||||
import QuickLinks from './QuickLinks'
|
||||
import './TargetSelection.css'
|
||||
import { firstStepCompletedSelector } from './targetSelectionSelectors'
|
||||
|
||||
export default compose(
|
||||
withTranslation(),
|
||||
|
@ -36,7 +36,7 @@ export default compose(
|
|||
formValueSelector('conversation')(state, dottedName),
|
||||
analysis: analysisWithDefaultsSelector(state),
|
||||
flatRules: flatRulesSelector(state),
|
||||
noUserInput: noUserInputSelector(state),
|
||||
firstStepCompleted: firstStepCompletedSelector(state),
|
||||
conversationStarted: state.conversationStarted,
|
||||
activeInput: state.activeTargetInput,
|
||||
objectifs: state.simulation?.config.objectifs || []
|
||||
|
@ -80,12 +80,18 @@ export default compose(
|
|||
}
|
||||
}
|
||||
render() {
|
||||
let { colours, noUserInput, analysis } = this.props,
|
||||
let {
|
||||
colours,
|
||||
firstStepCompleted,
|
||||
conversationStarted,
|
||||
analysis,
|
||||
explanation
|
||||
} = this.props,
|
||||
inversionFail = analysis.cache.inversionFail
|
||||
|
||||
return (
|
||||
<div id="targetSelection">
|
||||
{!noUserInput && (
|
||||
{firstStepCompleted && (
|
||||
<Controls
|
||||
inversionFail={inversionFail}
|
||||
controls={analysis.controls}
|
||||
|
@ -105,7 +111,8 @@ export default compose(
|
|||
}}>
|
||||
{this.renderOutputList()}
|
||||
</section>
|
||||
<QuickLinks />
|
||||
<QuickLinks show={firstStepCompleted && !conversationStarted} />
|
||||
{firstStepCompleted && explanation}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -258,7 +265,7 @@ let TargetInputOrValue = withLanguage(
|
|||
activeInput,
|
||||
setActiveInput,
|
||||
language,
|
||||
noUserInput,
|
||||
firstStepCompleted,
|
||||
inversionFail
|
||||
}) => (
|
||||
<span className="targetInputOrValue">
|
||||
|
@ -278,7 +285,7 @@ let TargetInputOrValue = withLanguage(
|
|||
target,
|
||||
activeInput,
|
||||
setActiveInput,
|
||||
noUserInput,
|
||||
firstStepCompleted,
|
||||
inversionFail
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { createSelector } from 'reselect'
|
||||
import {
|
||||
formattedSituationSelector,
|
||||
targetNamesSelector,
|
||||
parsedRulesSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
|
||||
export let firstStepCompletedSelector = createSelector(
|
||||
[
|
||||
formattedSituationSelector,
|
||||
targetNamesSelector,
|
||||
parsedRulesSelector,
|
||||
state => state.simulation?.config?.bloquant
|
||||
],
|
||||
(situation, targetNames, parsedRules, bloquant) => {
|
||||
if (!situation) {
|
||||
return true
|
||||
}
|
||||
const situations = Object.keys(situation)
|
||||
const allBlockingAreAnswered =
|
||||
bloquant && bloquant.every(rule => situations.includes(rule))
|
||||
const targetIsAnswered =
|
||||
targetNames &&
|
||||
targetNames.some(
|
||||
targetName =>
|
||||
findRuleByDottedName(parsedRules, targetName)?.formule &&
|
||||
targetName in situation
|
||||
)
|
||||
return allBlockingAreAnswered || targetIsAnswered
|
||||
}
|
||||
)
|
|
@ -21,7 +21,9 @@ import {
|
|||
intersection,
|
||||
isNil,
|
||||
mergeDeepWith,
|
||||
pick
|
||||
pick,
|
||||
isEmpty,
|
||||
dissoc
|
||||
} from 'ramda'
|
||||
import { getFormValues } from 'redux-form'
|
||||
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect'
|
||||
|
@ -65,28 +67,8 @@ export let formattedSituationSelector = createSelector(
|
|||
)
|
||||
|
||||
export let noUserInputSelector = createSelector(
|
||||
[
|
||||
formattedSituationSelector,
|
||||
targetNamesSelector,
|
||||
parsedRulesSelector,
|
||||
state => state.simulation?.config?.bloquant
|
||||
],
|
||||
(situation, targetNames, parsedRules, bloquant) => {
|
||||
if (!situation) {
|
||||
return true
|
||||
}
|
||||
const situations = Object.keys(situation)
|
||||
const allBlockingAreAnswered =
|
||||
bloquant && bloquant.every(rule => situations.includes(rule))
|
||||
const targetIsAnswered =
|
||||
targetNames &&
|
||||
targetNames.some(
|
||||
targetName =>
|
||||
findRuleByDottedName(parsedRules, targetName)?.formule &&
|
||||
targetName in situation
|
||||
)
|
||||
return !(allBlockingAreAnswered || targetIsAnswered)
|
||||
}
|
||||
[formattedSituationSelector],
|
||||
situation => !situation || isEmpty(dissoc('période', situation))
|
||||
)
|
||||
|
||||
let validatedStepsSelector = createSelector(
|
||||
|
|
|
@ -60,6 +60,7 @@ const Footer = ({ colours: { colour }, tracker, t, sitePaths }) => {
|
|||
))}
|
||||
</Helmet>
|
||||
<PageFeedback
|
||||
stickToFooter={true}
|
||||
blacklist={feedbackBlacklist.map(lens => view(lens, sitePaths))}
|
||||
/>
|
||||
<footer className="footer" style={{ backgroundColor: `${colour}22` }}>
|
||||
|
|
|
@ -36,20 +36,23 @@ const AssimiléSalarié = ({ t }) => (
|
|||
<Warning />
|
||||
<Simulation
|
||||
targetsTriggerConversation={true}
|
||||
targets={<TargetSelection />}
|
||||
explanation={
|
||||
<>
|
||||
<p>
|
||||
{emoji('☂️ ')}{' '}
|
||||
<T k="simulateurs.assimilé-salarié.explications">
|
||||
Les gérants égalitaires ou minoritaires de SARL ou les dirigeants
|
||||
de SA et SAS sont assimilés salariés et relèvent du régime
|
||||
général. Par conséquent, le dirigeant a la même protection sociale
|
||||
qu'un salarié, mis à part le chômage.
|
||||
</T>
|
||||
</p>
|
||||
<SalaryExplanation />
|
||||
</>
|
||||
targets={
|
||||
<TargetSelection
|
||||
explanation={
|
||||
<>
|
||||
<p>
|
||||
{emoji('☂️ ')}{' '}
|
||||
<T k="simulateurs.assimilé-salarié.explications">
|
||||
Les gérants égalitaires ou minoritaires de SARL ou les
|
||||
dirigeants de SA et SAS sont assimilés salariés et relèvent du
|
||||
régime général. Par conséquent, le dirigeant a la même
|
||||
protection sociale qu'un salarié, mis à part le chômage.
|
||||
</T>
|
||||
</p>
|
||||
<SalaryExplanation />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -35,8 +35,11 @@ const AutoEntrepreneur = ({ t }) => (
|
|||
<Warning simulateur="auto-entreprise" />
|
||||
<Simulation
|
||||
targetsTriggerConversation={true}
|
||||
targets={<TargetSelection />}
|
||||
explanation={<AvertissementProtectionSocialeIndépendants />}
|
||||
targets={
|
||||
<TargetSelection
|
||||
explanation={<AvertissementProtectionSocialeIndépendants />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -35,12 +35,15 @@ const Indépendant = ({ t }) => (
|
|||
<Warning />
|
||||
<Simulation
|
||||
targetsTriggerConversation={true}
|
||||
targets={<TargetSelection />}
|
||||
explanation={
|
||||
<>
|
||||
<AvertissementForfaitIndépendants />
|
||||
<AvertissementProtectionSocialeIndépendants />
|
||||
</>
|
||||
targets={
|
||||
<TargetSelection
|
||||
explanation={
|
||||
<>
|
||||
<AvertissementForfaitIndépendants />
|
||||
<AvertissementProtectionSocialeIndépendants />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { React, T } from 'Components'
|
||||
import SalaryExplanation from 'Components/SalaryExplanation'
|
||||
import Simulation from 'Components/Simulation'
|
||||
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
|
||||
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
|
||||
|
@ -9,6 +8,7 @@ import { compose } from 'ramda'
|
|||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
import SalaryExplanation from 'Components/SalaryExplanation'
|
||||
|
||||
export let SalarySimulation = withSitePaths(({ sitePaths }) => (
|
||||
<Simulation
|
||||
|
@ -29,8 +29,7 @@ export let SalarySimulation = withSitePaths(({ sitePaths }) => (
|
|||
)}
|
||||
</>
|
||||
}
|
||||
targets={<TargetSelection />}
|
||||
explanation={<SalaryExplanation />}
|
||||
targets={<TargetSelection explanation={<SalaryExplanation />} />}
|
||||
/>
|
||||
))
|
||||
|
||||
|
@ -55,14 +54,6 @@ const Salarié = ({ t }) => (
|
|||
<T k="simulateurs.salarié.titre">Simulateur de salaire</T>
|
||||
</h1>
|
||||
<SalarySimulation />
|
||||
<p>
|
||||
<T k="simulateurs.salarié.description">
|
||||
Dès que l'embauche d'un salarié est déclarée et qu'il est payé, il est
|
||||
couvert par le régime général de la Sécurité sociale (santé, maternité,
|
||||
invalidité, vieillesse, maladie professionnelle et accidents) et
|
||||
chômage.
|
||||
</T>
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
export default compose(
|
||||
|
|
Loading…
Reference in New Issue