📏 Active le mode strict de TypeScript sur la partie mon-entreprise

pull/1240/head
Maxime Quandalle 2020-11-23 15:16:13 +01:00
parent d7f140334e
commit b2cee93d58
45 changed files with 519 additions and 276 deletions

1
.yarnclean Normal file
View File

@ -0,0 +1 @@
@types/react-native

View File

@ -26,14 +26,15 @@
"@babel/preset-typescript": "^7.9.0",
"@types/cheerio": "^0.22.18",
"@types/js-yaml": "^3.12.2",
"@types/react": "^16.9.11",
"@types/react": "^17.0.0",
"@types/react-color": "^3.0.1",
"@types/react-dom": "^16.9.3",
"@types/react-dom": "^17.0.0",
"@types/react-helmet": "^5.0.13",
"@types/react-redux": "^7.1.5",
"@types/react-redux": "^7.1.11",
"@types/react-router": "^5.1.2",
"@types/recharts": "^1.8.9",
"@types/styled-components": "^5.1.0",
"@types/recharts": "^1.8.16",
"@types/redux-sentry-middleware": "^0.1.2",
"@types/styled-components": "^5.1.4",
"@types/webpack": "^4.41.10",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
@ -44,7 +45,7 @@
"cypress-plugin-tab": "^1.0.5",
"eslint-plugin-react": "^7.12.4",
"html-webpack-plugin": "^3.2.0",
"i18next-parser": "^1.0.6",
"i18next-parser": "^3.3.0",
"intl-locales-supported": "^1.0.0",
"mock-local-storage": "^1.0.5",
"monaco-editor-webpack-plugin": "^1.9.0",
@ -79,7 +80,7 @@
"react-helmet": "6.0.0-beta",
"react-i18next": "^11.0.0",
"react-markdown": "^4.1.0",
"react-monaco-editor": "^0.36.0",
"react-monaco-editor": "^0.40.0",
"react-number-format": "^4.3.1",
"react-redux": "^7.0.3",
"react-router-dom": "^5.1.1",

View File

@ -1,43 +0,0 @@
import { fetchCompanyDetails } from '../api/sirene'
const fetchCommuneDetails = function(codeCommune) {
return fetch(
`https://geo.api.gouv.fr/communes/${codeCommune}?fields=departement,region`
).then(response => {
return response.json()
})
}
export const setEntreprise = siren => async dispatch => {
dispatch({
type: 'EXISTING_COMPANY::SET_SIREN',
siren
})
const companyDetails = await fetchCompanyDetails(siren)
dispatch({
type: 'EXISTING_COMPANY::SET_DETAILS',
catégorieJuridique: companyDetails.categorie_juridique,
dateDeCréation: companyDetails.date_creation
})
const communeDetails = await fetchCommuneDetails(
companyDetails.etablissement_siege.code_commune
)
dispatch({
type: 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS',
details: communeDetails
})
}
export const specifyIfAutoEntrepreneur = isAutoEntrepreneur => ({
type: 'EXISTING_COMPANY::SPECIFY_AUTO_ENTREPRENEUR',
isAutoEntrepreneur
})
export const specifyIfDirigeantMajoritaire = isDirigeantMajoritaire => ({
type: 'EXISTING_COMPANY::SPECIFY_DIRIGEANT_MAJORITAIRE',
isDirigeantMajoritaire
})
export const resetEntreprise = () => ({
type: 'EXISTING_COMPANY::RESET'
})

View File

@ -0,0 +1,69 @@
import { ApiCommuneJson } from 'Components/conversation/select/SelectCommune'
import { fetchCompanyDetails } from '../api/sirene'
const fetchCommuneDetails = function(codeCommune: string) {
return fetch(
`https://geo.api.gouv.fr/communes/${codeCommune}?fields=departement,region`
).then(response => {
return response.json()
})
}
export type ActionExistingCompany =
| ReturnType<typeof specifyIfAutoEntrepreneur>
| ReturnType<typeof specifyIfDirigeantMajoritaire>
| ReturnType<typeof resetEntreprise>
| {
type: 'EXISTING_COMPANY::SET_SIREN'
siren: string
}
| {
type: 'EXISTING_COMPANY::SET_DETAILS'
catégorieJuridique: string
dateDeCréation: string
}
| {
type: 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS'
details: ApiCommuneJson
}
export const setEntreprise = (siren: string) => async (
dispatch: (action: ActionExistingCompany) => void
) => {
dispatch({
type: 'EXISTING_COMPANY::SET_SIREN',
siren
} as ActionExistingCompany)
const companyDetails = await fetchCompanyDetails(siren)
dispatch({
type: 'EXISTING_COMPANY::SET_DETAILS',
catégorieJuridique: companyDetails.categorie_juridique,
dateDeCréation: companyDetails.date_creation
})
const communeDetails: ApiCommuneJson = await fetchCommuneDetails(
companyDetails.etablissement_siege.code_commune
)
dispatch({
type: 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS',
details: communeDetails
} as ActionExistingCompany)
}
export const specifyIfAutoEntrepreneur = (isAutoEntrepreneur: boolean) =>
({
type: 'EXISTING_COMPANY::SPECIFY_AUTO_ENTREPRENEUR',
isAutoEntrepreneur
} as const)
export const specifyIfDirigeantMajoritaire = (
isDirigeantMajoritaire: boolean
) =>
({
type: 'EXISTING_COMPANY::SPECIFY_DIRIGEANT_MAJORITAIRE',
isDirigeantMajoritaire
} as const)
export const resetEntreprise = () =>
({
type: 'EXISTING_COMPANY::RESET'
} as const)

View File

@ -14,7 +14,7 @@ const formInfos = {
}
export default function NewsletterRegister() {
const [userIsRegistered, setUserIsRegistered] = usePersistingState(
const [userIsRegistered, setUserIsRegistered] = usePersistingState<boolean>(
'app::newsletter::registered',
false
)

View File

@ -1,5 +1,6 @@
import * as animate from 'Components/ui/animate'
import FocusTrap from 'focus-trap-react'
import { PageInfo } from 'iframe-resizer'
import React, { useEffect, useState } from 'react'
import styled, { css } from 'styled-components'
@ -16,7 +17,7 @@ const useIFrameOffset = () => {
setOffset(0)
return
}
window.parentIFrame.getPageInfo(({ scrollTop, offsetTop }) => {
window.parentIFrame.getPageInfo(({ scrollTop, offsetTop }: PageInfo) => {
setOffset(scrollTop - offsetTop)
window.parentIFrame.getPageInfo(false)
})
@ -127,8 +128,9 @@ const StyledOverlayWrapper = styled.div<{ offsetTop: number | null }>`
max-width: 40em;
min-height: 6em;
}
.ui__.card[aria-modal='true'] {
padding-bottom: 2rem;
margin-bottom: 2rem;
.ui__.card[aria-modal='true'] {
padding-bottom: 2rem;
margin-bottom: 2rem;
}
}
`

View File

@ -2,9 +2,16 @@ import { formatValue } from 'publicodes'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { debounce as debounceFn } from '../utils'
import { InputCommonProps } from './conversation/RuleInput'
import './PercentageField.css'
export default function PercentageField({ onChange, value, debounce = 0 }) {
type PercentageFieldProps = InputCommonProps & { debounce: number }
export default function PercentageField({
onChange,
value,
debounce = 0
}: PercentageFieldProps) {
const [localValue, setLocalValue] = useState(value)
const debouncedOnChange = useCallback(
debounce ? debounceFn(debounce, onChange) : onChange,

View File

@ -11,6 +11,7 @@ import {
useInversionFail
} from 'Components/utils/EngineContext'
import { SitePathsContext } from 'Components/utils/SitePathsContext'
import { EvaluatedNode } from 'publicodes'
import { EvaluatedRule, formatValue } from 'publicodes'
import { isNil } from 'ramda'
import { Fragment, useCallback, useContext } from 'react'
@ -300,7 +301,9 @@ function AidesGlimpse() {
// faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui
// est une somme des aides qui sont toutes nulle sauf l'aide active.
const aidesDetail = aides?.formule.explanation.explanation
const aidesNotNul = aidesDetail?.filter(node => node.nodeValue !== false)
const aidesNotNul = aidesDetail?.filter(
(node: EvaluatedNode) => node.nodeValue !== false
)
const aideLink = aidesNotNul?.length === 1 ? aidesNotNul[0] : aides
if (!aides?.nodeValue) return null

View File

@ -3,7 +3,7 @@ import {
updateSituation,
validateStepWithValue
} from 'Actions/actions'
import RuleInput from 'Components/conversation/RuleInput'
import RuleInput, { RuleInputProps } from 'Components/conversation/RuleInput'
import QuickLinks from 'Components/QuickLinks'
import * as Animate from 'Components/ui/animate'
import { EngineContext } from 'Components/utils/EngineContext'
@ -38,9 +38,12 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
}, [dispatch, currentQuestion])
const setDefault = () =>
dispatch(
// TODO: Skiping a question shouldn't be equivalent to answering the
// default value (for instance the question shouldn't appear in the
// answered questions).
validateStepWithValue(
currentQuestion,
rules[currentQuestion]['par défaut']
rules[currentQuestion].defaultValue
)
)
const goToPrevious = () =>
@ -55,7 +58,7 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
})
}
const onChange = value => {
const onChange: RuleInputProps['onChange'] = value => {
dispatch(updateSituation(currentQuestion, value))
}

View File

@ -1,22 +1,22 @@
import { formatValue } from 'publicodes'
import { formatValue, Unit } from 'publicodes'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import NumberFormat from 'react-number-format'
import { currencyFormat, debounce } from '../../utils'
import InputSuggestions from './InputSuggestions'
import { InputCommonProps } from './RuleInput'
// TODO: fusionner Input.js et CurrencyInput.js
export default function Input({
suggestions,
onChange,
onSubmit,
dottedName,
id,
value,
defaultValue,
autoFocus,
unit
}) {
}: InputCommonProps & { unit?: Unit; onSubmit: (source: string) => void }) {
const debouncedOnChange = useCallback(debounce(550, onChange), [])
const { language } = useTranslation().i18n
const { thousandSeparator, decimalSeparator } = currencyFormat(language)

View File

@ -1,14 +1,14 @@
import { useCallback } from 'react'
import { debounce } from '../../utils'
import { InputCommonProps } from './RuleInput'
export default function ParagrapheInput({
onChange,
dottedName,
value,
id,
defaultValue,
autoFocus
}) {
}: InputCommonProps) {
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
return (

View File

@ -1,11 +1,11 @@
import classnames from 'classnames'
import { Markdown } from 'Components/utils/markdown'
import { is } from 'ramda'
import { useCallback, useEffect, useState } from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { Explicable } from './Explicable'
import { References } from 'publicodes'
import { References, ParsedRule, Rule } from 'publicodes'
import { binaryQuestion, InputCommonProps, RuleInputProps } from './RuleInput'
/* 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:
@ -23,13 +23,23 @@ import { References } from 'publicodes'
*/
export type Choice = ParsedRule & {
canGiveUp?: boolean
children: Array<Choice>
}
type QuestionProps = InputCommonProps & {
onSubmit: (source: string) => void
choices: Choice | typeof binaryQuestion
}
export default function Question({
choices,
onSubmit,
dottedName: questionDottedName,
onChange,
value: currentValue
}) {
}: QuestionProps) {
const [currentSelection, setCurrentSelection] = useState(currentValue)
const handleChange = useCallback(
value => {
@ -53,7 +63,7 @@ export default function Question({
}
}, [currentSelection])
const renderBinaryQuestion = () => {
const renderBinaryQuestion = (choices: typeof binaryQuestion) => {
return choices.map(({ value, label }) => (
<span
key={value}
@ -82,10 +92,10 @@ export default function Question({
</span>
))
}
const renderChildren = choices => {
const renderChildren = (choices: Choice) => {
// seront stockées ainsi dans le state :
// [parent object path]: dotted fieldName relative to parent
const relativeDottedName = radioDottedName =>
const relativeDottedName = (radioDottedName: string) =>
radioDottedName.split(questionDottedName + ' . ')[1]
return (
<ul css="width: 100%; padding: 0; margin:0" className="ui__ radio">
@ -110,7 +120,7 @@ export default function Question({
children ? (
<li key={dottedName} className="variant">
<div>{title}</div>
{renderChildren({ children })}
{renderChildren({ children } as Choice)}
</li>
) : (
<li key={dottedName} className="variantLeaf">
@ -135,8 +145,8 @@ export default function Question({
)
}
const choiceElements = is(Array)(choices)
? renderBinaryQuestion()
const choiceElements = Array.isArray(choices)
? renderBinaryQuestion(choices)
: renderChildren(choices)
return (
@ -154,7 +164,13 @@ export default function Question({
)
}
export const RadioLabel = props => (
type RadioLabelProps = RadioLabelContentProps & {
description?: string
label?: string
références?: Rule['références']
}
export const RadioLabel = (props: RadioLabelProps) => (
<>
<RadioLabelContent {...props} />
{props.description && (
@ -174,6 +190,16 @@ export const RadioLabel = props => (
</>
)
type RadioLabelContentProps = {
value: string
label: string
name: string
currentSelection?: string
icons?: string
onChange: RuleInputProps['onChange']
onSubmit: (src: string, value: string) => void
}
function RadioLabelContent({
value,
label,
@ -182,7 +208,7 @@ function RadioLabelContent({
icons,
onChange,
onSubmit
}) {
}: RadioLabelContentProps) {
const labelStyle = value === '_' ? ({ fontWeight: 'bold' } as const) : {}
const selected = value === currentSelection

View File

@ -1,5 +1,5 @@
import Input from 'Components/conversation/Input'
import Question from 'Components/conversation/Question'
import Question, { Choice } from 'Components/conversation/Question'
import SelectCommune from 'Components/conversation/select/SelectCommune'
import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
import CurrencyInput from 'Components/CurrencyInput/CurrencyInput'
@ -15,7 +15,7 @@ import TextInput from './TextInput'
import SelectEuropeCountry from './select/SelectEuropeCountry'
import ParagrapheInput from './ParagrapheInput'
type Value = string | number | Record<string, unknown> | boolean | null
type Value = any
export type RuleInputProps<Name extends string = DottedName> = {
rules: ParsedRules<Name>
dottedName: Name
@ -24,11 +24,29 @@ export type RuleInputProps<Name extends string = DottedName> = {
isTarget?: boolean
autoFocus?: boolean
id?: string
value?: Value
value: Value
className?: string
onSubmit?: (source: string) => void
}
export type InputCommonProps = Pick<
RuleInputProps<string>,
'dottedName' | 'value' | 'onChange' | 'autoFocus' | 'className'
> &
Pick<
ParsedRule<string>,
'title' | 'question' | 'defaultValue' | 'suggestions'
> & {
key: string
id: string
required: boolean
}
export const binaryQuestion = [
{ value: 'oui', label: 'Oui' },
{ value: 'non', label: 'Non' }
] as const
// This function takes the unknown rule and finds which React component should
// be displayed to get a user input through successive if statements
// That's not great, but we won't invest more time until we have more diverse
@ -49,7 +67,7 @@ export default function RuleInput<Name extends string = DottedName>({
const unit = rule.unit
const language = useTranslation().i18n.language
const engine = useContext(EngineContext)
const commonProps = {
const commonProps: InputCommonProps = {
key: dottedName,
dottedName,
value,
@ -153,7 +171,7 @@ const getVariant = (rule: ParsedRule) =>
export const buildVariantTree = <Name extends string>(
allRules: ParsedRules<Name>,
path: Name
) => {
): Choice => {
const rec = (path: Name) => {
const node = allRules[path]
if (!node) throw new Error(`La règle ${path} est introuvable`)
@ -168,7 +186,7 @@ export const buildVariantTree = <Name extends string>(
children: variants.map((v: string) => rec(`${path} . ${v}` as Name))
}
: null
)
) as Choice
}
return rec(path)
}

View File

@ -1,15 +1,14 @@
import { ThemeColorsContext } from 'Components/utils/colors'
import { useCallback, useContext } from 'react'
import { useCallback } from 'react'
import { debounce } from '../../utils'
import { InputCommonProps } from './RuleInput'
export default function TextInput({
onChange,
dottedName,
value,
id,
defaultValue,
autoFocus
}) {
}: InputCommonProps) {
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
return (

View File

@ -3,6 +3,22 @@ import React, { useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { debounce } from '../../../utils'
import styled, { css } from 'styled-components'
import { InputCommonProps } from '../RuleInput'
export type ApiCommuneJson = {
_score: number
code: string
codesPostaux: Array<string>
departement: {
code: string
nom: string
}
nom: string
region: {
code: string
nom: string
}
}
type Commune = {
code: string
@ -10,7 +26,7 @@ type Commune = {
nom: string
}
async function tauxVersementTransport(codeCommune) {
async function tauxVersementTransport(codeCommune: Commune['code']) {
const response = await fetch(
'https://versement-transport.netlify.app/.netlify/functions/taux-par-code-commune?codeCommune=' +
codeCommune
@ -35,7 +51,7 @@ async function searchCommunes(input: string): Promise<Array<Commune> | null> {
if (!response.ok) {
return null
}
const json = await response.json()
const json: Array<ApiCommuneJson> = await response.json()
return json
.flatMap(({ codesPostaux, ...commune }) =>
codesPostaux
@ -46,7 +62,7 @@ async function searchCommunes(input: string): Promise<Array<Commune> | null> {
.slice(0, 10)
}
export default function Select({ onChange, value, id }) {
export default function Select({ onChange, value, id }: InputCommonProps) {
const [name, setName] = useState(formatCommune(value))
const [searchResults, setSearchResults] = useState<null | Array<Commune>>(
null

View File

@ -1,3 +1,5 @@
import { InputCommonProps } from '../RuleInput'
const STATES = [
'Allemagne',
'Autriche',
@ -32,7 +34,11 @@ const STATES = [
'Suisse'
] as const
export default function SelectEuropeCountry({ value, onChange, id }) {
export default function SelectEuropeCountry({
value,
onChange,
id
}: InputCommonProps) {
return (
<div>
<select

View File

@ -129,7 +129,14 @@ function PLExplanation() {
function CaisseRetraite() {
const engine = useContext(EngineContext)
const unit = useSelector(targetUnitSelector)
const caisses = ['CARCDSF', 'CARPIMKO', 'CIPAV', 'CARMF', 'CNBF', 'CAVEC']
const caisses = [
'CARCDSF',
'CARPIMKO',
'CIPAV',
'CARMF',
'CNBF',
'CAVEC'
] as const
return (
<>

View File

@ -40,7 +40,7 @@ export function LinkRenderer({
if (
href &&
href.startsWith(`https://${domain}`) &&
internalURLs[domain] === siteName
internalURLs[domain as keyof typeof internalURLs] === siteName
) {
return (
<Link to={href.replace(`https://${domain}`, '')} {...otherProps}>

View File

@ -1,14 +1,6 @@
import { useEffect, useState } from 'react'
import safeLocalStorage from '../../storage/safeLocalStorage'
export const persistState = (key: string) => ([state, changeState]) => {
useEffect(() => {
safeLocalStorage.setItem(key, JSON.stringify(state))
return
}, [state])
return [state, changeState]
}
export const getInitialState = (key: string) => {
const value = safeLocalStorage.getItem(key)
if (!value) {
@ -22,9 +14,17 @@ export const getInitialState = (key: string) => {
}
}
export const usePersistingState = (key: string, defaultState?: any) => {
const initialState = getInitialState(key)
return persistState(key)(
useState(initialState != null ? initialState : defaultState)
)
export const useSafeLocaleStorage = (key: string, state: any) => {
useEffect(() => {
if (key) {
safeLocalStorage.setItem(key, JSON.stringify(state))
}
}, [state])
}
export const usePersistingState = <S>(key: string, defaultState?: any) => {
const initialState = getInitialState(key)
const state = initialState != null ? initialState : defaultState
useSafeLocaleStorage(key, state)
return useState<S>(state)
}

View File

@ -39,8 +39,8 @@ type MissingVariables = Partial<Record<DottedName, number>>
export function getNextSteps(
missingVariables: Array<MissingVariables>
): Array<DottedName> {
const byCount = ([, [count]]) => count
const byScore = ([, [, score]]) => score
const byCount = ([, [count]]: [unknown, [number]]) => count
const byScore = ([, [, score]]: [unknown, [unknown, number]]) => score
const missingByTotalScore = reduce<MissingVariables, MissingVariables>(
mergeWith(add),

View File

@ -1,5 +1,7 @@
import { Action as CreationChecklistAction } from 'Actions/companyCreationChecklistActions'
import { ActionExistingCompany } from 'Actions/existingCompanyActions'
import { Action as HiringChecklist } from 'Actions/hiringChecklistAction'
import { ApiCommuneJson } from 'Components/conversation/select/SelectCommune'
import { omit } from 'ramda'
import { combineReducers } from 'redux'
import { LegalStatus } from 'Selectors/companyStatusSelectors'
@ -8,7 +10,11 @@ import {
LegalStatusRequirements
} from 'Types/companyTypes'
type Action = CompanyStatusAction | CreationChecklistAction | HiringChecklist
type Action =
| CompanyStatusAction
| CreationChecklistAction
| HiringChecklist
| ActionExistingCompany
function companyLegalStatus(
state: LegalStatusRequirements = {},
@ -120,10 +126,6 @@ const infereLegalStatusFromCategorieJuridique = (
return 'NON_IMPLÉMENTÉ'
}
type GeoDetails = {
nom: string
code: string
}
export type Company = {
siren: string
catégorieJuridique?: string
@ -131,23 +133,23 @@ export type Company = {
dateDeCréation?: string
isAutoEntrepreneur?: boolean
isDirigeantMajoritaire?: boolean
localisation?: GeoDetails & {
departement: GeoDetails
region: GeoDetails
}
localisation?: ApiCommuneJson
}
function existingCompany(state: Company | null = null, action): Company | null {
function existingCompany(
state: Company | null = null,
action: Action
): Company | null {
if (!action.type.startsWith('EXISTING_COMPANY::')) {
return state
}
if (action.type.endsWith('RESET')) {
if (action.type === 'EXISTING_COMPANY::RESET') {
return null
}
if (action.type.endsWith('SET_SIREN')) {
if (action.type === 'EXISTING_COMPANY::SET_SIREN') {
return { siren: action.siren }
}
if (state && action.type.endsWith('SET_DETAILS')) {
if (state && action.type === 'EXISTING_COMPANY::SET_DETAILS') {
const statutJuridique = infereLegalStatusFromCategorieJuridique(
action.catégorieJuridique
)
@ -158,13 +160,16 @@ function existingCompany(state: Company | null = null, action): Company | null {
dateDeCréation: action.dateDeCréation
}
}
if (state && action.type.endsWith('SPECIFY_AUTO_ENTREPRENEUR')) {
if (state && action.type === 'EXISTING_COMPANY::SPECIFY_AUTO_ENTREPRENEUR') {
return { ...state, isAutoEntrepreneur: action.isAutoEntrepreneur }
}
if (state && action.type.endsWith('SPECIFY_DIRIGEANT_MAJORITAIRE')) {
if (
state &&
action.type === 'EXISTING_COMPANY::SPECIFY_DIRIGEANT_MAJORITAIRE'
) {
return { ...state, isDirigeantMajoritaire: action.isDirigeantMajoritaire }
}
if (state && action.type.endsWith('ADD_COMMUNE_DETAILS')) {
if (state && action.type === 'EXISTING_COMPANY::ADD_COMMUNE_DETAILS') {
return { ...state, localisation: action.details }
}
return state

View File

@ -71,7 +71,7 @@ export type Simulation = {
foldedSteps: Array<DottedName>
unfoldedStep?: DottedName | null
}
function getCompanySituation(company: Company): Situation {
function getCompanySituation(company: Company | null): Situation {
return {
...(company?.localisation && {
'établissement . localisation': company.localisation
@ -172,7 +172,7 @@ function simulation(
}
return state
}
const existingCompanyReducer = (state, action: Action) => {
const existingCompanyReducer = (state: RootState, action: Action) => {
if (action.type.startsWith('EXISTING_COMPANY::') && state.simulation) {
return {
...state,
@ -187,7 +187,7 @@ const existingCompanyReducer = (state, action: Action) => {
}
return state
}
const mainReducer = (state, action: Action) =>
const mainReducer = (state: any, action: Action) =>
combineReducers({
explainedVariable,
// We need to access the `rules` in the simulation reducer

View File

@ -95,13 +95,15 @@ const QUESTION_LIST: Array<Question> = keys(
mergeAll(flatten(Object.values(LEGAL_STATUS_DETAILS)))
)
const isCompatibleStatusWith = answers => (statusRequirements): boolean => {
const isCompatibleStatusWith = (answers: any) => (
statusRequirements: LegalStatusRequirements
): boolean => {
const stringify = map(x => (!isNil(x) ? JSON.stringify(x) : x))
const answerCompatibility = Object.values(
mergeWith(
(answer, statusValue) =>
isNil(answer) || isNil(statusValue) || answer === statusValue,
stringify(statusRequirements),
stringify(statusRequirements as any),
stringify(answers)
)
)
@ -109,13 +111,13 @@ const isCompatibleStatusWith = answers => (statusRequirements): boolean => {
return isCompatibleStatus
}
const possibleStatus = (
answers: LegalStatusRequirements
answers: Array<LegalStatusRequirements> | LegalStatusRequirements
): Record<LegalStatus, boolean> =>
map(
statusRequirements =>
Array.isArray(statusRequirements)
? any(isCompatibleStatusWith(answers), statusRequirements)
: isCompatibleStatusWith(answers)(
? any(isCompatibleStatusWith(answers as any), statusRequirements)
: isCompatibleStatusWith(answers as any)(
statusRequirements as LegalStatusRequirements
),
LEGAL_STATUS_DETAILS
@ -133,7 +135,7 @@ export const nextQuestionSelector = (state: RootState): Question | null => {
>
const possibleStatusList = flatten(
Object.values(LEGAL_STATUS_DETAILS)
).filter(isCompatibleStatusWith(legalStatusRequirements))
).filter(isCompatibleStatusWith(legalStatusRequirements) as any)
const unansweredQuestions = difference(QUESTION_LIST, questionAnswered)
const shannonEntropyByQuestion = unansweredQuestions.map((question): [
@ -141,7 +143,7 @@ export const nextQuestionSelector = (state: RootState): Question | null => {
number
] => {
const answerPopulation = Object.values(possibleStatusList).map(
status => status[question]
(status: any) => status[question]
)
const frequencyOfAnswers = Object.values(
countBy(

View File

@ -1,4 +1,4 @@
import { DottedName } from '../rules/index'
import { DottedName, Situation } from '../rules/index'
import { createSelector } from 'reselect'
import { RootState, SimulationConfig } from 'Reducers/rootReducer'
@ -16,7 +16,7 @@ export const objectifsSelector = createSelector([configSelector], config => {
return objectifs
})
const emptySituation = {}
const emptySituation: Situation = {}
export const situationSelector = (state: RootState) =>
state.simulation?.situation ?? emptySituation

View File

@ -29,7 +29,6 @@ import {
retrievePersistedSimulation
} from '../../storage/persistSimulation'
import Tracker, { devTracker } from '../../Tracker'
import { inIframe } from '../../utils'
import './App.css'
import Footer from './layout/Footer/Footer'
import Header from './layout/Header'
@ -72,7 +71,7 @@ if (process.env.NODE_ENV === 'production') {
}
const middlewares = [
createSentryMiddleware(Sentry),
createSentryMiddleware(Sentry as any),
trackSimulatorActions(tracker)
]

View File

@ -1,8 +1,9 @@
import { DottedName } from 'Rules'
import { situationSelector } from 'Selectors/simulationSelectors'
import Tracker from 'Tracker'
export default (tracker: Tracker) => {
return ({ getState }) => next => action => {
return ({ getState }: any) => (next: any) => (action: any) => {
next(action)
const newState = getState()
if (action.type == 'STEP_ACTION' && action.name == 'fold') {
@ -11,7 +12,7 @@ export default (tracker: Tracker) => {
'Simulator::answer',
action.source,
action.step,
situationSelector(newState)[action.step]
situationSelector(newState)[action.step as DottedName]
])
// TODO : add tracking in UI instead ?

View File

@ -6,7 +6,7 @@ import { TrackerContext } from 'Components/utils/withTracker'
import { lazy, Suspense, useContext, useRef, useState } from 'react'
import emoji from 'react-easy-emoji'
import SignaturePad from 'react-signature-pad-wrapper'
import PDFDocument from './PDFDocument'
import PDFDocument, { PDFDocumentProps } from './PDFDocument'
const IS_TOUCH_DEVICE = isOnTouchDevice()
type SignaturePadInstance = {
@ -14,7 +14,12 @@ type SignaturePadInstance = {
toDataURL: () => string
}
export default function EndBlock({ fields, isMissingValues }) {
type EndBlockProps = {
fields: PDFDocumentProps['fields']
isMissingValues: boolean
}
export default function EndBlock({ fields, isMissingValues }: EndBlockProps) {
const [isCertified, setCertified] = useState(false)
const [place, setPlace] = useState<string>()
const [showDownloadLink, toggleDownloadLink] = useState(false)
@ -196,7 +201,7 @@ const LazyBlobProvider = lazy<typeof BlobProvider>(
// From https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
function isOnTouchDevice() {
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
const mq = function(query) {
const mq = function(query: string) {
return window.matchMedia(query).matches
}
if (

View File

@ -1,33 +1,41 @@
import { StyleSheet, Text, View } from '@react-pdf/renderer'
import { formatValue } from 'publicodes'
import { formatValue, EvaluatedRule } from 'publicodes'
export default function FieldsPDF({ fields }) {
return fields.map(field => (
<View style={styles.field} key={field.dottedName} wrap={false}>
{field.type === 'groupe' ? (
<>
<Text style={styles.subtitle}>
{field.title}{' '}
{field.note && (
<Text style={styles.fieldNumber}>({field.note})</Text>
)}
</Text>
</>
) : (
<>
<Text style={styles.name}>
{field.question ?? field.title}{' '}
{field.note && (
<Text style={styles.fieldNumber}>({field.note})</Text>
)}
</Text>
{field.nodeValue != null && (
<Text style={styles.value}>{formatValue(field)}</Text>
export type FieldsPDFProps = {
fields: Array<EvaluatedRule<string>>
}
export default function FieldsPDF({ fields }: FieldsPDFProps) {
return (
<>
{fields.map(field => (
<View style={styles.field} key={field.dottedName} wrap={false}>
{field.type === 'groupe' ? (
<>
<Text style={styles.subtitle}>
{field.title}{' '}
{field.note && (
<Text style={styles.fieldNumber}>({field.note})</Text>
)}
</Text>
</>
) : (
<>
<Text style={styles.name}>
{field.question ?? field.title}{' '}
{field.note && (
<Text style={styles.fieldNumber}>({field.note})</Text>
)}
</Text>
{field.nodeValue != null && (
<Text style={styles.value}>{formatValue(field)}</Text>
)}
</>
)}
</>
)}
</View>
))
</View>
))}
</>
)
}
export const styles = StyleSheet.create({

View File

@ -1,4 +1,4 @@
import {
import ReactPDF, {
Document,
Font,
Image,
@ -9,11 +9,21 @@ import {
Link
} from '@react-pdf/renderer'
import urssafPng from 'Images/destinataires/URSSAF.png'
import FieldsPDF, { styles as fieldStyles } from './FieldsPDF'
import FieldsPDF, { FieldsPDFProps, styles as fieldStyles } from './FieldsPDF'
import montserratUrl from './Montserrat-SemiBold.ttf'
import robotoUrl from './Roboto-Regular.ttf'
export default function PDFDocument({ fields, signatureURL, place }) {
export type PDFDocumentProps = {
fields: FieldsPDFProps['fields']
signatureURL?: ReactPDF.SourceObject | false
place?: string
}
export default function PDFDocument({
fields,
signatureURL,
place
}: PDFDocumentProps) {
return (
<Document>
<Page style={styles.body} wrap>

View File

@ -92,8 +92,8 @@ const useFields = (engine: Engine<string>, fieldNames: Array<string>) => {
}
const VERSION = hash(JSON.stringify(formulaire))
function FormulairePublicodes({ engine }) {
const [situation, setSituation] = usePersistingState(
function FormulairePublicodes({ engine }: { engine: Engine<string> }) {
const [situation, setSituation] = usePersistingState<Record<string, string>>(
`formulaire-détachement:${VERSION}`,
{}
)

View File

@ -62,7 +62,7 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
const rule = useEvaluation(dottedName)
const initialRender = useContext(InitialRenderContext)
const parsedRules = useContext(EngineContext).getParsedRules()
const value = useSelector(situationSelector)[dottedName] ?? rule['par défaut']
const value = useSelector(situationSelector)[dottedName] ?? rule.defaultValue
if (rule.isApplicable === false || rule.isApplicable === null) {
return null
}

View File

@ -11,9 +11,11 @@ import Home from './Home'
export default function Simulateurs() {
const sitePaths = useContext(SitePathsContext)
const { state, pathname } = useLocation()
const [lastState, setLastState] = usePersistingState(
'navigation::simulateurs::locationState::v2'
)
const [lastState, setLastState] = usePersistingState<{
fromGérer?: boolean
fromCréer?: boolean
fromSimulateurs?: boolean
}>('navigation::simulateurs::locationState::v2')
useEffect(() => {
if (state) {
setLastState(state)

View File

@ -80,7 +80,7 @@ export type SimulatorData = Record<
>
export function getSimulatorsData({
t = (_, text) => text,
t = (_: unknown, text: string) => text,
sitePaths = constructLocalizedSitePath('fr'),
language = 'fr'
}): SimulatorData {

View File

@ -16,11 +16,16 @@ import { estExonéréeSelector } from './selectors'
import { StoreContext } from './StoreContext'
import { formatValue } from 'publicodes'
export type Activity = {
titre: string
explication: string
}
export default function Activité({
match: {
params: { title }
}
}) {
}: any) {
const { language } = useTranslation().i18n
const sitePaths = useContext(SitePathsContext)
const { state, dispatch } = useContext(StoreContext)
@ -43,7 +48,7 @@ export default function Activité({
<section className="ui__ full-width light-bg">
<ActivitéSelection
currentActivité={title}
activités={activité.activités.map(({ titre }) => titre)}
activités={activité.activités.map(({ titre }: Activity) => titre)}
/>
</section>
</Animate.fromBottom>

View File

@ -11,6 +11,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { debounce } from '../../../../../utils'
import { toggleActivité } from './actions'
import { Activity } from './Activité'
import { activités, getTranslatedActivité } from './activitésData'
import NextButton from './NextButton'
import {
@ -56,7 +57,9 @@ export default function ActivitésSelection() {
Quels types d'activités avez-vous exercé ?
</Trans>
</h2>
<ActivitéSelection activités={activités.map(({ titre }) => titre)} />
<ActivitéSelection
activités={activités.map(({ titre }: Activity) => titre)}
/>
<p className="ui__ container notice" css="text-align: center">
<Trans i18nKey="économieCollaborative.accueil.réassurance">
PS : cet outil est là uniquement pour vous informer, aucune donnée
@ -183,7 +186,7 @@ const ActivitéContent = ({
plateformes,
icônes,
label
}) => (
}: any) => (
<>
<h4 className="title">
{titre}{' '}

View File

@ -3,15 +3,16 @@ import { Trans } from 'react-i18next'
import { CheckItem, Checklist } from 'Components/ui/Checklist'
import { changeCritèreExonération } from './actions'
import { StoreContext } from './StoreContext'
import { Activity } from './Activité'
export default function ExceptionsExonération({
exceptionsExonération,
activité
}) {
}: any) {
const { state, dispatch } = useContext(StoreContext)
if (!exceptionsExonération) return null
const defaultChecked = state[activité].critèresExonération.map(
estRespecté => !estRespecté
(estRespecté: boolean) => !estRespecté
)
return (
@ -27,18 +28,20 @@ export default function ExceptionsExonération({
}
defaultChecked={defaultChecked}
>
{exceptionsExonération.map(({ titre, explication }, index) => (
<CheckItem
key={index}
name={index}
title={titre}
explanations={explication}
/>
))}
{exceptionsExonération.map(
({ titre, explication }: Activity, index: string) => (
<CheckItem
key={index}
name={index}
title={titre}
explanations={explication}
/>
)
)}
</Checklist>
<p className="ui__ notice">
<Trans i18nKey="économieCollaborative.exonération.notice">
Si aucun de ces cas ne s'appliquent à vous, vous n'aurez rien à
Si aucun de ces cas ne s'applique à vous, vous n'aurez rien à
déclarer.
</Trans>
</p>

View File

@ -1,23 +0,0 @@
import { getInitialState, persistState } from 'Components/utils/persistState'
import { createContext, useCallback, useReducer } from 'react'
const StoreContext = createContext()
const StoreProvider = ({ children, reducer, localStorageKey }) => {
const computeInitialState = useCallback(
() => reducer(getInitialState(localStorageKey), { type: '@@INIT_STATE' }),
[reducer]
)
const [state, dispatch] = (localStorageKey
? persistState(localStorageKey)
: x => x)(useReducer(reducer, undefined, computeInitialState))
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
)
}
export { StoreContext, StoreProvider }

View File

@ -0,0 +1,38 @@
import {
useSafeLocaleStorage,
getInitialState
} from 'Components/utils/persistState'
import { Reducer } from 'react'
import { createContext, useCallback, useReducer, ReactNode } from 'react'
const StoreContext = createContext<{
state: any
dispatch: any
}>({ state: null, dispatch: null })
type StoreProviderProps = {
children: ReactNode
reducer: Reducer<any, any>
localStorageKey: string
}
const StoreProvider = ({
children,
reducer,
localStorageKey
}: StoreProviderProps) => {
const computeInitialState = useCallback(
() => reducer(getInitialState(localStorageKey), { type: '@@INIT_STATE' }),
[reducer]
)
const [state, dispatch] = useReducer(reducer, undefined, computeInitialState)
useSafeLocaleStorage(localStorageKey, state)
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
)
}
export { StoreContext, StoreProvider }

View File

@ -8,7 +8,7 @@ import {
getSousActivités
} from './activitésData'
const activitéReducer = reducerActivité =>
const activitéReducer = (reducerActivité: any) =>
combineReducers({
effectuée: (state = false, { type, activité }: Action): boolean =>
type === 'TOGGLE_ACTIVITÉ_EFFECTUÉE' && reducerActivité === activité
@ -44,21 +44,24 @@ const activitéReducer = reducerActivité =>
action.type === 'CHANGE_CRITÈRE_EXONÉRATION' &&
reducerActivité === action.activité
) {
state[action.index] = action.estRespecté
state[action.index as any] = action.estRespecté
return [...state]
}
return state
}
})
// type ActivityTitle = string
// type State = Record<ActivityTitle, ReturnType<ReturnType<typeof activitéReducer>>>
type ActivityTitle = string
type State = Record<
ActivityTitle,
ReturnType<ReturnType<typeof activitéReducer>>
>
const reducer = reduceReducers(
((state, { type, activité }: Action) => {
((state: State, { type, activité }: Action) => {
if (type === 'TOGGLE_ACTIVITÉ_EFFECTUÉE' && state[activité].effectuée) {
return getSousActivités(activité).reduce(
(newState, sousActivité) => ({
(newState: State, sousActivité: any) => ({
...newState,
[sousActivité]: {
...state[sousActivité],

View File

@ -19,7 +19,6 @@ import {
XAxis,
YAxis
} from 'recharts'
import DefaultTooltipContent from 'recharts/lib/component/DefaultTooltipContent'
import styled from 'styled-components'
import statsJson from '../../../../data/stats.json'
import { capitalise0 } from '../../../../utils'
@ -385,17 +384,11 @@ const CustomTooltip = ({
return null
}
return (
<DefaultTooltipContent
payload={[
{
value: formatDate(payload[0].payload.date, periodicity)
},
{
value: formatValue(payload[0].payload.visiteurs),
unit: ' visiteurs'
}
]}
/>
<p className="ui__ card">
<strong>{formatValue(payload[0].payload.visiteurs)} visites</strong>
<br />
{formatDate(payload[0].payload.date, periodicity)}
</p>
)
}

View File

@ -0,0 +1,4 @@
declare module 'react-signature-pad-wrapper' {
const signaturePad: any
export default signaturePad
}

View File

@ -13,7 +13,8 @@
"Selectors/*": ["selectors/*"],
"Types/*": ["types/*"]
},
"noEmit": true
"noEmit": true,
"strict": true
},
"include": ["types", "source"]
}

View File

@ -27,15 +27,15 @@
"@types/nearley": "^2.11.1",
"@types/ramda": "^0.26.43",
"@types/raven-for-redux": "^1.1.1",
"@types/react": "^16.9.11",
"@types/react": "^17.0.0",
"@types/react-color": "^3.0.1",
"@types/react-dom": "^16.9.3",
"@types/react-dom": "^17.0.0",
"@types/react-helmet": "^5.0.13",
"@types/react-redux": "^7.1.5",
"@types/react-redux": "^7.1.11",
"@types/react-router": "^5.1.2",
"@types/react-router-hash-link": "^1.2.1",
"@types/react-syntax-highlighter": "^11.0.4",
"@types/recharts": "^1.8.9",
"@types/recharts": "^1.8.16",
"@types/styled-components": "^5.1.0",
"@types/webpack": "^4.41.10",
"@types/webpack-bundle-analyzer": "^2.13.3",

View File

@ -1 +1 @@
import 'styled-components/cssprop'
import {} from 'styled-components/cssprop'

139
yarn.lock
View File

@ -1360,6 +1360,16 @@
"@sentry/utils" "5.15.5"
tslib "^1.9.3"
"@sentry/browser@^5.0.0":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.27.6.tgz#54fe177e9986246586b0761eb38cbad1ad07ecb5"
integrity sha512-pqrojE2ZmLUVz7l/ogtogK0+M2pK3bigYm0fja7vG7F7kXnCAwqAHDYfkFXEvFI8WvNwH+niy28lSoV95lnm0Q==
dependencies:
"@sentry/core" "5.27.6"
"@sentry/types" "5.27.6"
"@sentry/utils" "5.27.6"
tslib "^1.9.3"
"@sentry/core@5.15.5":
version "5.15.5"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.5.tgz#40ea79bff5272d3fbbeeb4a98cdc59e1adbd2c92"
@ -1371,6 +1381,17 @@
"@sentry/utils" "5.15.5"
tslib "^1.9.3"
"@sentry/core@5.27.6":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.27.6.tgz#3ceeb58acd857f1e17d52d3087bfecb506adc1f7"
integrity sha512-izCS5iyc6HAfpW1AsGXLAKetx82C1Sq1siAh97tOlSK58PVJAEH/WMiej9WuZJxCDTOtj94QtoLflssrZyAtFg==
dependencies:
"@sentry/hub" "5.27.6"
"@sentry/minimal" "5.27.6"
"@sentry/types" "5.27.6"
"@sentry/utils" "5.27.6"
tslib "^1.9.3"
"@sentry/hub@5.15.5":
version "5.15.5"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.5.tgz#f5abbcdbe656a70e2ff02c02a5a4cffa0f125935"
@ -1380,6 +1401,15 @@
"@sentry/utils" "5.15.5"
tslib "^1.9.3"
"@sentry/hub@5.27.6":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.27.6.tgz#a94adbe32c45dda7ad5adf742b82e0a022eb9c2f"
integrity sha512-bOMky3iu7zEghSaWmTayfme5tCpUok841qDCGxGKuyAtOhBDsgGNS/ApNEEDF2fyX0oo4G1cHYPWhX90ZFf/xA==
dependencies:
"@sentry/types" "5.27.6"
"@sentry/utils" "5.27.6"
tslib "^1.9.3"
"@sentry/minimal@5.15.5":
version "5.15.5"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.5.tgz#a0e4e071f01d9c4d808094ae7203f6c4cca9348a"
@ -1389,11 +1419,25 @@
"@sentry/types" "5.15.5"
tslib "^1.9.3"
"@sentry/minimal@5.27.6":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.27.6.tgz#783012ed94668be168f2b521e0ea6295c76ce2b0"
integrity sha512-pKhzVQX9nL4m1dcnb2i2Y47IWVNs+K3wiYLgCB9hl9+ApxppfOc+fquiFoCloST3IuaD4yly2TtbOJgAMWcMxQ==
dependencies:
"@sentry/hub" "5.27.6"
"@sentry/types" "5.27.6"
tslib "^1.9.3"
"@sentry/types@5.15.5":
version "5.15.5"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.5.tgz#16c97e464cf09bbd1d2e8ce90d130e781709076e"
integrity sha512-F9A5W7ucgQLJUG4LXw1ZIy4iLevrYZzbeZ7GJ09aMlmXH9PqGThm1t5LSZlVpZvUfQ2rYA8NU6BdKJSt7B5LPw==
"@sentry/types@5.27.6":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.27.6.tgz#b5054eafcb8ac11d4bc4787c7bc7fc113cad8b80"
integrity sha512-XOW9W8DrMk++4Hk7gWi9o5VR0o/GrqGfTKyFsHSIjqt2hL6kiMPvKeb2Hhmp7Iq37N2bDmRdWpM5m+68S2Jk6w==
"@sentry/utils@5.15.5":
version "5.15.5"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.15.5.tgz#dec1d4c79037c4da08b386f5d34409234dcbfb15"
@ -1402,6 +1446,14 @@
"@sentry/types" "5.15.5"
tslib "^1.9.3"
"@sentry/utils@5.27.6":
version "5.27.6"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.27.6.tgz#cd8486469ae9716a21a4bc7e828e5aeee0ed9727"
integrity sha512-/QMVLv+zrTfiIj2PU+SodSbSzD5MmamMOaljkDsRIVsj6gpkm1/VG1g2+40TZ0FbQ4hCW2F+iR7cnqzZBNmchA==
dependencies:
"@sentry/types" "5.27.6"
tslib "^1.9.3"
"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@ -1500,17 +1552,17 @@
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/d3-path@*":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.8.tgz#48e6945a8ff43ee0a1ce85c8cfa2337de85c7c79"
integrity sha512-AZGHWslq/oApTAHu9+yH/Bnk63y9oFOMROtqPAtxl5uB6qm1x2lueWdVEjsjjV3Qc2+QfuzKIwIR5MvVBakfzA==
"@types/d3-path@^1":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c"
integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==
"@types/d3-shape@*":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.2.tgz#a41d9d6b10d02e221696b240caf0b5d0f5a588ec"
integrity sha512-LtD8EaNYCaBRzHzaAiIPrfcL3DdIysc81dkGlQvv7WQP3+YXV7b0JJTtR1U3bzeRieS603KF4wUo+ZkJVenh8w==
"@types/d3-shape@^1":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.5.tgz#c0164c1be1429473016f855871d487f806c4e968"
integrity sha512-aPEax03owTAKynoK8ZkmkZEDZvvT4Y5pWgii4Jp4oQt0gH45j6siDl9gNDVC5kl64XHN2goN9jbYoHK88tFAcA==
dependencies:
"@types/d3-path" "*"
"@types/d3-path" "^1"
"@types/history@*":
version "4.7.7"
@ -1605,10 +1657,10 @@
"@types/react" "*"
"@types/reactcss" "*"
"@types/react-dom@^16.9.3":
version "16.9.8"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
"@types/react-dom@^17.0.0":
version "17.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a"
integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==
dependencies:
"@types/react" "*"
@ -1626,10 +1678,10 @@
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.5":
version "7.1.9"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3"
integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==
"@types/react-redux@^7.1.11":
version "7.1.11"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.11.tgz#a18e8ab3651e8e8cc94798934927937c66021217"
integrity sha512-OjaFlmqy0CRbYKBoaWF84dub3impqnLJUrz4u8PRjDzaa4n1A2cVmjMV81shwXyAD5x767efhA8STFGJz/r1Zg==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
@ -1668,10 +1720,10 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.11", "@types/react@^16.x":
version "16.9.46"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.46.tgz#f0326cd7adceda74148baa9bff6e918632f5069e"
integrity sha512-dbHzO3aAq1lB3jRQuNpuZ/mnu+CdD3H0WVaaBQA8LTT3S33xhVBUj232T8M3tAhSWJs/D/UqORYUlJNl/8VQZg==
"@types/react@*", "@types/react@^17.0.0":
version "17.0.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
@ -1683,14 +1735,22 @@
dependencies:
"@types/react" "*"
"@types/recharts@^1.8.9":
version "1.8.14"
resolved "https://registry.yarnpkg.com/@types/recharts/-/recharts-1.8.14.tgz#7fcbf9346db415d417910f5ce17af4e3e8e42c57"
integrity sha512-Q4NR9vgmugOELYY/8lXzs99nf9E1/fDWx6ZTVEjAtOhAbqXYFrepyQKa4+bR4mHl1F2WcYgf96fvWIu7It6rmA==
"@types/recharts@^1.8.16":
version "1.8.16"
resolved "https://registry.yarnpkg.com/@types/recharts/-/recharts-1.8.16.tgz#3ac3f5513ed61152910f2e828157b21a2761df22"
integrity sha512-xBXjOsSJJVP2xGtq/kML4rHGyKxA4x8ZqIU7iwbmWrperpSXzoQ1PWjqf4cBdmcLAWaidDHPJxUVHkZr3R/PEA==
dependencies:
"@types/d3-shape" "*"
"@types/d3-shape" "^1"
"@types/react" "*"
"@types/redux-sentry-middleware@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/redux-sentry-middleware/-/redux-sentry-middleware-0.1.2.tgz#64fb3d63df41e633d53bcbbfac3f49cf3dc9a1ef"
integrity sha512-cTrRRJxSrzoMznvreNHrg1izy/cjUghFGXDqQI+6di3BvnjBqcI1mTrU/QVKPf/P7Sk/TNyY7c2IQaYK0Q3Acw==
dependencies:
"@sentry/browser" "^5.0.0"
redux "^4.0.0"
"@types/sizzle@2.3.2":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
@ -1716,6 +1776,16 @@
"@types/react-native" "*"
csstype "^3.0.2"
"@types/styled-components@^5.1.4":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.4.tgz#11f167dbde268635c66adc89b5a5db2e69d75384"
integrity sha512-78f5Zuy0v/LTQNOYfpH+CINHpchzMMmAt9amY2YNtSgsk1TmlKm8L2Wijss/mtTrsUAVTm2CdGB8VOM65vA8xg==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
"@types/react-native" "*"
csstype "^3.0.2"
"@types/tapable@*":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
@ -6450,10 +6520,10 @@ hyphen@^1.1.1:
resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.6.2.tgz#321d794d92b778f39cb5db740a575c092463978c"
integrity sha512-WcyRGy0kB+QcSlgV9ovP4UQ7E+LpDCv7q9gziO2Q2eqpJ4AtHVfVhNXxlW0EBKgUMmcEHecQKy9AWdm2EavW1Q==
i18next-parser@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-1.0.6.tgz#520575d6560b716b83fbbd32a2b6df500c97f134"
integrity sha512-fI8e3I574kru+QRK33DeTHC912AxjzYP4mh/ufnsvWQ0B1FbWZPUKqikCnsxePCL9ssMDqS7N70W0Fgxqj48Fg==
i18next-parser@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-3.3.0.tgz#5cd907aff5e791c043137a4902ab42631cde9656"
integrity sha512-o8hh9PW5Gqs3fVV5ULxPfBV957n97TT7LTSj/M84ZOKFM6c5Ab6d1gMREr+GEKO3T+niOzF5biG1MRkzojjscA==
dependencies:
broccoli-plugin "^1.3.0"
cheerio "^1.0.0-rc.2"
@ -10234,12 +10304,11 @@ react-markdown@^4.1.0, react-markdown@^4.3.1:
unist-util-visit "^1.3.0"
xtend "^4.0.1"
react-monaco-editor@^0.36.0:
version "0.36.0"
resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.36.0.tgz#ac085c14f25fb072514c925596f6a06a711ee078"
integrity sha512-JVA5SZhOoYZ0DCdTwYgagtRb3jHo4KN7TVFiJauG+ZBAJWfDSTzavPIrwzWbgu8ahhDqDk4jUcYlOJL2BC/0UA==
react-monaco-editor@^0.40.0:
version "0.40.0"
resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.40.0.tgz#f1b021b32952cfc63a4bf2661fd20f61b2b8309f"
integrity sha512-IG322vOwKc/yjhn91xbqHONyAVxjv5L0YOUBU+hDwfswlglm/sGsqGhK9n1lD5d3l3kegMO/ZeZaMHC2LGgNRw==
dependencies:
"@types/react" "^16.x"
monaco-editor "*"
prop-types "^15.7.2"