✅ Ajoute des types TypeScript
Transforme quelques derniers composants class en fonctionspull/960/head
parent
10372882ff
commit
0a03b7550c
|
@ -134,6 +134,8 @@
|
|||
"@types/react-redux": "^7.1.5",
|
||||
"@types/react-router": "^5.1.2",
|
||||
"@types/react-router-dom": "^5.1.0",
|
||||
"@types/react-router-hash-link": "^1.2.1",
|
||||
"@types/react-syntax-highlighter": "^11.0.4",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"akh": "^3.1.2",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { ThemeColorsProvider } from 'Components/utils/colors'
|
||||
import { SitePathProvider, SitePaths } from 'Components/utils/withSitePaths'
|
||||
import { TrackerProvider } from 'Components/utils/withTracker'
|
||||
import { createBrowserHistory, History } from 'history'
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { AvailableLangs } from 'i18n'
|
||||
import i18next from 'i18next'
|
||||
import React, { PureComponent } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
import { Provider as ReduxProvider } from 'react-redux'
|
||||
import { Router } from 'react-router-dom'
|
||||
|
@ -41,7 +41,7 @@ if (
|
|||
})
|
||||
}
|
||||
|
||||
type ProviderProps = {
|
||||
export type ProviderProps = {
|
||||
tracker?: Tracker
|
||||
basename: string
|
||||
sitePaths: SitePaths
|
||||
|
@ -49,39 +49,50 @@ type ProviderProps = {
|
|||
initialStore: RootState
|
||||
onStoreCreated: (store: Store) => void
|
||||
reduxMiddlewares: Array<Middleware>
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default class Provider extends PureComponent<ProviderProps> {
|
||||
history: History
|
||||
store: Store
|
||||
constructor(props: ProviderProps) {
|
||||
super(props)
|
||||
this.history = createBrowserHistory({
|
||||
basename: process.env.NODE_ENV === 'production' ? '' : this.props.basename
|
||||
})
|
||||
this.props.tracker?.connectToHistory(this.history)
|
||||
const storeEnhancer = composeEnhancers(
|
||||
applyMiddleware(
|
||||
// Allows us to painlessly do route transition in action creators
|
||||
thunk.withExtraArgument({
|
||||
history: this.history,
|
||||
sitePaths: this.props.sitePaths
|
||||
}),
|
||||
...(props.reduxMiddlewares ?? [])
|
||||
)
|
||||
)
|
||||
if (this.props.language) {
|
||||
i18next.changeLanguage(this.props.language)
|
||||
if (this.props.initialStore)
|
||||
this.props.initialStore.lang = this.props.language
|
||||
export default function Provider({
|
||||
tracker,
|
||||
basename,
|
||||
sitePaths,
|
||||
reduxMiddlewares,
|
||||
language,
|
||||
initialStore,
|
||||
onStoreCreated,
|
||||
children
|
||||
}: ProviderProps) {
|
||||
const history = createBrowserHistory({
|
||||
basename: process.env.NODE_ENV === 'production' ? '' : basename
|
||||
})
|
||||
useEffect(() => {
|
||||
tracker?.connectToHistory(history)
|
||||
return () => {
|
||||
tracker?.disconnectFromHistory()
|
||||
}
|
||||
this.store = createStore(reducers, this.props.initialStore, storeEnhancer)
|
||||
this.props.onStoreCreated?.(this.store)
|
||||
})
|
||||
|
||||
// Remove loader
|
||||
var css = document.createElement('style')
|
||||
css.type = 'text/css'
|
||||
css.innerHTML = `
|
||||
const storeEnhancer = composeEnhancers(
|
||||
applyMiddleware(
|
||||
// Allows us to painlessly do route transition in action creators
|
||||
thunk.withExtraArgument({
|
||||
history,
|
||||
sitePaths
|
||||
}),
|
||||
...(reduxMiddlewares ?? [])
|
||||
)
|
||||
)
|
||||
if (language) {
|
||||
i18next.changeLanguage(language)
|
||||
if (initialStore) initialStore.lang = language
|
||||
}
|
||||
const store = createStore(reducers, initialStore, storeEnhancer)
|
||||
onStoreCreated?.(store)
|
||||
|
||||
// Remove loader
|
||||
const css = document.createElement('style')
|
||||
css.type = 'text/css'
|
||||
css.innerHTML = `
|
||||
#js {
|
||||
animation: appear 0.5s;
|
||||
opacity: 1;
|
||||
|
@ -89,33 +100,28 @@ export default class Provider extends PureComponent<ProviderProps> {
|
|||
#loading {
|
||||
display: none !important;
|
||||
}`
|
||||
document.body.appendChild(css)
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.props.tracker?.disconnectFromHistory()
|
||||
}
|
||||
render() {
|
||||
const iframeCouleur =
|
||||
new URLSearchParams(document?.location.search.substring(1)).get(
|
||||
'couleur'
|
||||
) ?? undefined
|
||||
return (
|
||||
// If IE < 11 display nothing
|
||||
<ReduxProvider store={this.store}>
|
||||
<ThemeColorsProvider
|
||||
color={iframeCouleur && decodeURIComponent(iframeCouleur)}
|
||||
>
|
||||
<TrackerProvider value={this.props.tracker as Tracker}>
|
||||
<SitePathProvider value={this.props.sitePaths}>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<Router history={this.history}>
|
||||
<>{this.props.children}</>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
</SitePathProvider>
|
||||
</TrackerProvider>
|
||||
</ThemeColorsProvider>
|
||||
</ReduxProvider>
|
||||
)
|
||||
}
|
||||
document.body.appendChild(css)
|
||||
const iframeCouleur =
|
||||
new URLSearchParams(document?.location.search.substring(1)).get(
|
||||
'couleur'
|
||||
) ?? undefined
|
||||
|
||||
return (
|
||||
// If IE < 11 display nothing
|
||||
<ReduxProvider store={store}>
|
||||
<ThemeColorsProvider
|
||||
color={iframeCouleur && decodeURIComponent(iframeCouleur)}
|
||||
>
|
||||
<TrackerProvider value={tracker!}>
|
||||
<SitePathProvider value={sitePaths}>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<Router history={history}>
|
||||
<>{children}</>
|
||||
</Router>
|
||||
</I18nextProvider>
|
||||
</SitePathProvider>
|
||||
</TrackerProvider>
|
||||
</ThemeColorsProvider>
|
||||
</ReduxProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
|
||||
const languageCodeToEmoji = {
|
||||
en: '🇬🇧',
|
||||
fr: '🇫🇷'
|
||||
}
|
||||
|
||||
export default withTranslation()(
|
||||
class LangSwitcher extends Component {
|
||||
getUnusedLanguageCode = () => {
|
||||
let languageCode = this.props.i18n.language
|
||||
return !languageCode || languageCode === 'fr' ? 'en' : 'fr'
|
||||
}
|
||||
|
||||
changeLanguage = () => {
|
||||
let nextLanguage = this.getUnusedLanguageCode()
|
||||
this.props.i18n.changeLanguage(nextLanguage)
|
||||
this.forceUpdate()
|
||||
}
|
||||
render() {
|
||||
const languageCode = this.getUnusedLanguageCode()
|
||||
return (
|
||||
<button
|
||||
className={this.props.className || 'ui__ link-button'}
|
||||
onClick={this.changeLanguage}>
|
||||
{emoji(languageCodeToEmoji[languageCode])}{' '}
|
||||
{languageCode.toUpperCase()}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const languageCodeToEmoji = {
|
||||
en: '🇬🇧',
|
||||
fr: '🇫🇷'
|
||||
}
|
||||
|
||||
export default function LangSwitcher({ className }: { className: string }) {
|
||||
const { i18n } = useTranslation()
|
||||
const languageCode = i18n.language
|
||||
const unusedLanguageCode =
|
||||
!languageCode || languageCode === 'fr' ? 'en' : 'fr'
|
||||
const changeLanguage = () => {
|
||||
i18n.changeLanguage(unusedLanguageCode)
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className={className ?? 'ui__ link-button'}
|
||||
onClick={changeLanguage}
|
||||
>
|
||||
{emoji(languageCodeToEmoji[languageCode as 'fr' | 'en'])}{' '}
|
||||
{languageCode.toUpperCase()}
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -5,7 +5,7 @@ import { DottedName } from 'Publicode/rules'
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect, useSelector } from 'react-redux'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import { Redirect, useParams } from 'react-router-dom'
|
||||
import {
|
||||
noUserInputSelector,
|
||||
parsedRulesSelector,
|
||||
|
@ -15,12 +15,12 @@ import Rule from './rule/Rule'
|
|||
import './RulePage.css'
|
||||
import SearchButton from './SearchButton'
|
||||
|
||||
export default function RulePage({ match }) {
|
||||
export default function RulePage() {
|
||||
const parsedRules = useSelector(parsedRulesSelector)
|
||||
const brancheName = useSelector(situationBranchNameSelector)
|
||||
const valuesToShow = !useSelector(noUserInputSelector)
|
||||
let name = match?.params?.name,
|
||||
decodedRuleName = decodeRuleName(name)
|
||||
const { name } = useParams()
|
||||
const decodedRuleName = decodeRuleName(name)
|
||||
|
||||
const renderRule = (dottedName: DottedName) => {
|
||||
return (
|
||||
|
@ -36,7 +36,8 @@ export default function RulePage({ match }) {
|
|||
)
|
||||
}
|
||||
|
||||
if (!parsedRules[decodedRuleName]) return <Redirect to="/404" />
|
||||
if (!parsedRules.hasOwnProperty(decodedRuleName))
|
||||
return <Redirect to="/404" />
|
||||
|
||||
return renderRule(decodedRuleName as DottedName)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,11 @@ import {
|
|||
import { softCatch } from '../../utils'
|
||||
import './AnswerList.css'
|
||||
|
||||
export default function AnswerList({ onClose }) {
|
||||
type AnswerListProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function AnswerList({ onClose }: AnswerListProps) {
|
||||
const dispatch = useDispatch()
|
||||
const { folded, next } = useSelector(stepsToRules)
|
||||
return (
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react'
|
|||
import { Trans } from 'react-i18next'
|
||||
import './Destinataire.css'
|
||||
|
||||
export default function Rule({ destinataire }) {
|
||||
export default function Rule({ destinataire }: { destinataire: string }) {
|
||||
let destinataireData = possiblesDestinataires[destinataire]
|
||||
|
||||
return destinataire && destinataireData ? (
|
||||
|
|
|
@ -14,7 +14,6 @@ export default function RuleHeader({
|
|||
description,
|
||||
question,
|
||||
flatRule,
|
||||
flatRules,
|
||||
acronyme,
|
||||
name,
|
||||
title,
|
||||
|
@ -26,7 +25,7 @@ export default function RuleHeader({
|
|||
<section id="ruleHeader">
|
||||
<header className="ui__ plain card">
|
||||
<div>
|
||||
<Namespace {...{ dottedName, flatRules, color: colors.textColor }} />
|
||||
<Namespace {...{ dottedName }} />
|
||||
<h1 style={{ color: colors.textColor }}>
|
||||
{title || capitalise0(name)}
|
||||
{acronyme && <> ({acronyme})</>}
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { DottedName } from 'Publicode/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { parsedRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import { capitalise0 } from '../../utils'
|
||||
import './Namespace.css'
|
||||
|
||||
export default function Namespace({ dottedName, flatRules, color }) {
|
||||
export default function Namespace({ dottedName }: { dottedName: DottedName }) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const colors = useContext(ThemeColorsContext)
|
||||
const flatRules = useSelector(parsedRulesSelector)
|
||||
return (
|
||||
<ul id="namespace">
|
||||
{dottedName
|
||||
.split(' . ')
|
||||
.slice(0, -1)
|
||||
.reduce(
|
||||
(memo, next) => [
|
||||
(memo: string[][], next: string) => [
|
||||
...memo,
|
||||
[...(memo.length ? memo.reverse()[0] : []), next]
|
||||
],
|
||||
[]
|
||||
)
|
||||
.map(fragments => {
|
||||
let ruleName = fragments.join(' . '),
|
||||
.map((fragments: string[]) => {
|
||||
let ruleName = fragments.join(' . ') as DottedName,
|
||||
rule = flatRules[ruleName]
|
||||
if (!rule) {
|
||||
throw new Error(
|
||||
|
@ -28,7 +34,7 @@ export default function Namespace({ dottedName, flatRules, color }) {
|
|||
)
|
||||
}
|
||||
let ruleText = rule.title || capitalise0(rule.name),
|
||||
style = { color }
|
||||
style = { color: colors.textColor }
|
||||
|
||||
return (
|
||||
<li style={style} key={fragments.join()}>
|
||||
|
|
|
@ -4,16 +4,21 @@ import React from 'react'
|
|||
import { capitalise0 } from '../../utils'
|
||||
import './References.css'
|
||||
|
||||
const findRefKey = link =>
|
||||
const findRefKey = (link: string) =>
|
||||
Object.keys(references).find(r => link.indexOf(r) > -1)
|
||||
|
||||
const cleanDomain = link =>
|
||||
const cleanDomain = (link: string) =>
|
||||
(link.indexOf('://') > -1 ? link.split('/')[2] : link.split('/')[0]).replace(
|
||||
'www.',
|
||||
''
|
||||
)
|
||||
|
||||
function Ref({ name, link }) {
|
||||
type RefProps = {
|
||||
name: string
|
||||
link: string
|
||||
}
|
||||
|
||||
function Ref({ name, link }: RefProps) {
|
||||
let refKey = findRefKey(link),
|
||||
refData = (refKey && references[refKey]) || {},
|
||||
domain = cleanDomain(link)
|
||||
|
|
|
@ -105,7 +105,6 @@ export default AttachDictionary(mecanisms)(function Rule({ dottedName }) {
|
|||
description,
|
||||
question,
|
||||
flatRule: rule,
|
||||
flatRules: rules,
|
||||
name,
|
||||
acronyme,
|
||||
title,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import './InfoBulle.css'
|
||||
|
||||
export default function InfoBulle({ children }) {
|
||||
export default function InfoBulle({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<span style={{ position: 'relative', verticalAlign: 'bottom' }}>
|
||||
<span className="info-bulle__interrogation-mark" tabIndex={0}>
|
||||
|
|
|
@ -6,7 +6,7 @@ import style from 'react-syntax-highlighter/dist/esm/styles/prism/atom-dark'
|
|||
|
||||
SyntaxHighlighter.registerLanguage('yaml', yaml)
|
||||
|
||||
export default ({ source }) => (
|
||||
export default ({ source }: { source: string }) => (
|
||||
<div css="position: relative; margin-bottom: 1rem">
|
||||
<SyntaxHighlighter language="yaml" style={style}>
|
||||
{source}
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
import { omit } from 'ramda'
|
||||
import React, { Component } from 'react'
|
||||
|
||||
const forEachParent = (node, fn) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
fn(node)
|
||||
forEachParent(node.parentNode, fn)
|
||||
}
|
||||
export class ScrollToTop extends Component {
|
||||
static defaultProps = {
|
||||
behavior: 'auto'
|
||||
}
|
||||
componentDidMount() {
|
||||
if ('parentIFrame' in window) {
|
||||
window.parentIFrame.scrollToOffset(0, 0)
|
||||
return
|
||||
}
|
||||
forEachParent(this.ref, elem => (elem.scrollTop = 0))
|
||||
try {
|
||||
window.scroll({
|
||||
top: 0,
|
||||
behavior: this.props.behavior
|
||||
})
|
||||
} catch (e) {
|
||||
window.scroll(0, 0)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={ref => {
|
||||
this.ref = ref
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class ScrollToElement extends Component {
|
||||
static defaultProps = {
|
||||
behavior: 'smooth',
|
||||
style: {},
|
||||
onlyIfNotVisible: false
|
||||
}
|
||||
scrollIfNeeded = () => {
|
||||
if (
|
||||
this.props.when === false ||
|
||||
(this.props.onlyIfNotVisible &&
|
||||
this.ref.getBoundingClientRect().top >= 0 &&
|
||||
this.ref.getBoundingClientRect().bottom <= window.innerHeight)
|
||||
) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.ref.scrollIntoView({
|
||||
behavior: this.props.behavior,
|
||||
block: 'nearest',
|
||||
inline: 'nearest'
|
||||
})
|
||||
} catch (error) {
|
||||
this.ref.scrollIntoView({
|
||||
behavior: this.props.behavior
|
||||
})
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
this.scrollIfNeeded()
|
||||
}
|
||||
componentDidUpdate() {
|
||||
this.scrollIfNeeded()
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
{...omit(['onlyIfNotVisible', 'when'], this.props)}
|
||||
style={{
|
||||
...this.props.style,
|
||||
...(!this.props.children ? { position: 'absolute' } : {})
|
||||
}}
|
||||
ref={ref => (this.ref = ref)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
toElement: ScrollToElement,
|
||||
toTop: ScrollToTop
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
const forEachParent = (node: Node | null, fn: (node: Node) => void) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
fn(node)
|
||||
forEachParent(node.parentNode, fn)
|
||||
}
|
||||
|
||||
export function ScrollToTop({
|
||||
behavior = 'auto'
|
||||
}: {
|
||||
behavior?: ScrollBehavior
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if ('parentIFrame' in window) {
|
||||
;(window as any).parentIFrame.scrollToOffset(0, 0)
|
||||
return
|
||||
}
|
||||
forEachParent(ref.current, elem => ((elem as any).scrollTop = 0))
|
||||
try {
|
||||
window.scroll({
|
||||
top: 0,
|
||||
behavior
|
||||
})
|
||||
} catch (e) {
|
||||
window.scroll(0, 0)
|
||||
}
|
||||
}, [])
|
||||
return <div ref={ref} />
|
||||
}
|
||||
|
||||
type ScrollToElementProps = React.ComponentProps<'div'> & {
|
||||
onlyIfNotVisible?: boolean
|
||||
when?: boolean
|
||||
behavior?: ScrollBehavior
|
||||
}
|
||||
|
||||
export function ScrollToElement({
|
||||
onlyIfNotVisible = false,
|
||||
when,
|
||||
behavior = 'smooth',
|
||||
children,
|
||||
style,
|
||||
...otherProps
|
||||
}: ScrollToElementProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const scrollIfNeeded = () => {
|
||||
if (
|
||||
when === false ||
|
||||
(onlyIfNotVisible &&
|
||||
ref.current &&
|
||||
ref.current.getBoundingClientRect().top >= 0 &&
|
||||
ref.current.getBoundingClientRect().bottom <= window.innerHeight)
|
||||
) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
ref.current?.scrollIntoView({
|
||||
behavior,
|
||||
block: 'nearest',
|
||||
inline: 'nearest'
|
||||
})
|
||||
} catch (error) {
|
||||
ref.current?.scrollIntoView({
|
||||
behavior
|
||||
})
|
||||
}
|
||||
}
|
||||
useEffect(scrollIfNeeded)
|
||||
|
||||
return (
|
||||
<div
|
||||
{...otherProps}
|
||||
style={{
|
||||
...style,
|
||||
...(!children ? { position: 'absolute' } : {})
|
||||
}}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
toElement: ScrollToElement,
|
||||
toTop: ScrollToTop
|
||||
}
|
|
@ -65,7 +65,7 @@ const generateTheme = (themeColor?: string) => {
|
|||
grayColor = '#00000099',
|
||||
textColor = findContrastedTextColor(color, true), // the 'simple' version feels better...
|
||||
inverseTextColor = textColor === '#ffffff' ? '#000' : '#fff',
|
||||
lightenTextColor = textColor =>
|
||||
lightenTextColor = (textColor: string) =>
|
||||
textColor === '#ffffff' ? 'rgba(255, 255, 255, .7)' : 'rgba(0, 0, 0, .7)',
|
||||
lighterTextColor = darkColor + 'cc',
|
||||
lighterInverseTextColor = lightenTextColor(inverseTextColor),
|
||||
|
|
|
@ -4,7 +4,7 @@ import emoji from 'react-easy-emoji'
|
|||
import ReactMarkdown, { ReactMarkdownProps } from 'react-markdown'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
function LinkRenderer({ href, children }) {
|
||||
function LinkRenderer({ href, children }: { href: string; children: string }) {
|
||||
if (!href.startsWith('http')) {
|
||||
return <Link to={href}>{children}</Link>
|
||||
}
|
||||
|
@ -25,14 +25,16 @@ function LinkRenderer({ href, children }) {
|
|||
</a>
|
||||
)
|
||||
}
|
||||
const TextRenderer = ({ children }) => <>{emoji(children)}</>
|
||||
const TextRenderer = ({ children }: { children: string }) => (
|
||||
<>{emoji(children)}</>
|
||||
)
|
||||
|
||||
type MarkdownProps = ReactMarkdownProps & {
|
||||
source: string | undefined
|
||||
className?: string
|
||||
}
|
||||
|
||||
const CodeBlock = ({ value, language }) =>
|
||||
const CodeBlock = ({ value, language }: { value: string; language: string }) =>
|
||||
language === 'yaml' ? (
|
||||
<PublicodeHighlighter source={value} />
|
||||
) : (
|
||||
|
|
|
@ -43,10 +43,10 @@ function getUnitKey(unit: string, lng: string): string {
|
|||
return key || unit
|
||||
}
|
||||
|
||||
let printUnits = (units: Array<string>, count: number, lng): string =>
|
||||
let printUnits = (units: Array<string>, count: number, lng: string): string =>
|
||||
units
|
||||
.filter(unit => unit !== '%')
|
||||
.map(unit => i18n.t(`units:${unit}`, { count, lng: lng }))
|
||||
.map(unit => i18n.t(`units:${unit}`, { count, lng }))
|
||||
.join('.')
|
||||
|
||||
const plural = 2
|
||||
|
@ -193,7 +193,11 @@ export function convertUnit(
|
|||
to: Unit | undefined,
|
||||
value: Evaluation<number>
|
||||
): Evaluation<number>
|
||||
export function convertUnit(from, to, value): any {
|
||||
export function convertUnit(
|
||||
from: Unit | undefined,
|
||||
to: Unit | undefined,
|
||||
value: number | Evaluation<number>
|
||||
) {
|
||||
if (!areUnitConvertible(from, to)) {
|
||||
throw new Error(
|
||||
`Impossible de convertir l'unité '${serializeUnit(
|
||||
|
|
|
@ -260,6 +260,9 @@ artiste-auteur . revenus . traitements et salaires:
|
|||
résumé.fr: Le montant brut hors TVA de vos droits d'auteur (recettes précomptées)
|
||||
titre.en: Income in wages and salaries
|
||||
titre.fr: Revenu en traitements et salaires
|
||||
chômage partiel:
|
||||
titre.en: '[automatic] short-time working'
|
||||
titre.fr: chômage partiel
|
||||
chômage partiel . coût employeur habituel:
|
||||
titre.en: '[automatic] regular employer cost'
|
||||
titre.fr: coût employeur habituel
|
||||
|
|
|
@ -106,7 +106,7 @@ function updateSituation(
|
|||
return { ...removePreviousTarget(situation), [fieldName]: value }
|
||||
}
|
||||
|
||||
function updateDefaultUnit(situation, { toUnit, analysis }) {
|
||||
function updateDefaultUnit(situation: Situation, { toUnit, analysis }) {
|
||||
const unit = parseUnit(toUnit)
|
||||
const goals = goalsFromAnalysis(analysis)
|
||||
const convertedSituation = Object.keys(situation)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,7 @@ import { Helmet } from 'react-helmet'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import 'Ui/index.css'
|
||||
import Provider from '../../Provider'
|
||||
import Provider, { ProviderProps } from '../../Provider'
|
||||
import {
|
||||
persistEverything,
|
||||
retrievePersistedState
|
||||
|
@ -55,7 +55,13 @@ const middlewares = [
|
|||
trackSimulatorActions(tracker)
|
||||
]
|
||||
|
||||
function InFranceRoute({ basename, language, rules }) {
|
||||
type InFranceRouteProps = {
|
||||
basename: ProviderProps['basename']
|
||||
language: ProviderProps['language']
|
||||
rules: ProviderProps['initialStore']['rules']
|
||||
}
|
||||
|
||||
function InFranceRoute({ basename, language, rules }: InFranceRouteProps) {
|
||||
useEffect(() => {
|
||||
getSessionStorage()?.setItem('lang', language)
|
||||
}, [language])
|
||||
|
|
|
@ -8,7 +8,6 @@ import translations from '../../locales/rules-en.yaml'
|
|||
import App from './App'
|
||||
|
||||
let anchor = document.querySelector('#js')
|
||||
console.log(translateRules('en', translations, rules))
|
||||
render(
|
||||
<App
|
||||
language="en"
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import React from 'react'
|
||||
import { ChromePicker } from 'react-color'
|
||||
import { ChromePicker, ChromePickerProps } from 'react-color'
|
||||
|
||||
export default function ColorPicker({ color, onChange }) {
|
||||
type ColorPickerProps = {
|
||||
color: ChromePickerProps['color']
|
||||
onChange: (color: string) => void
|
||||
}
|
||||
|
||||
export default function ColorPicker({ color, onChange }: ColorPickerProps) {
|
||||
return (
|
||||
<ChromePicker
|
||||
color={color}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import CompanyDetails from 'Components/CompanyDetails'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import { DottedName } from 'Publicode/rules'
|
||||
import React, { useRef } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
@ -64,7 +65,6 @@ export function AideDéclarationIndépendantsRécapitulatif() {
|
|||
/>
|
||||
|
||||
<SimpleField
|
||||
label="Il cotise sur la base"
|
||||
dottedName={
|
||||
'dirigeant . indépendant . conjoint collaborateur . assiette'
|
||||
}
|
||||
|
@ -90,11 +90,10 @@ export function AideDéclarationIndépendantsRécapitulatif() {
|
|||
}
|
||||
|
||||
type SimpleFieldProps = {
|
||||
label?: string
|
||||
dottedName: string
|
||||
dottedName: DottedName
|
||||
unit?: string
|
||||
}
|
||||
function SimpleField({ label, dottedName, unit }: SimpleFieldProps) {
|
||||
function SimpleField({ dottedName, unit }: SimpleFieldProps) {
|
||||
const situation = useSelector(situationSelector)
|
||||
const rules = useSelector((state: RootState) => state.rules)
|
||||
const value = situation[dottedName]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { ScrollToTop } from 'Components/utils/Scroll'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
@ -11,33 +12,38 @@ import { hideNewsBanner } from '../../layout/NewsBanner'
|
|||
const fetcher = (url: RequestInfo) => fetch(url).then(r => r.json())
|
||||
const slugify = (name: string) => name.toLowerCase().replace(' ', '-')
|
||||
|
||||
type ReleasesData = Array<{
|
||||
name: string
|
||||
description: string
|
||||
}>
|
||||
|
||||
export default function Nouveautés() {
|
||||
// The release.json file may be big, we don't want to include it in the main
|
||||
// bundle, that's why we only fetch it on this page. Alternatively we could
|
||||
// use import("data/release.json") and configure code splitting with Webpack.
|
||||
const { data } = useSWR('/data/releases.json', fetcher)
|
||||
const { data } = useSWR<ReleasesData>('/data/releases.json', fetcher)
|
||||
const history = useHistory()
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const slug = useRouteMatch<{ slug: string }>(`${sitePaths.nouveautés}/:slug`)
|
||||
?.params?.slug
|
||||
const selectedRelease = data?.findIndex(({ name }) => slugify(name) === slug)
|
||||
|
||||
useEffect(hideNewsBanner, [])
|
||||
useEffect(() => {
|
||||
window.scrollTo({ top: 0 })
|
||||
}, [selectedRelease])
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedRelease = data.findIndex(({ name }) => slugify(name) === slug)
|
||||
|
||||
const getPath = (index: number) =>
|
||||
`${sitePaths.nouveautés}/${slugify(data[index].name)}`
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
} else if (!slug || selectedRelease === -1) {
|
||||
if (!slug || selectedRelease === -1) {
|
||||
return <Redirect to={getPath(0)} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollToTop key={selectedRelease} />
|
||||
<h1>Les nouveautés {emoji('✨')}</h1>
|
||||
<p>
|
||||
Nous améliorons le site en continu à partir de vos retours. Découvrez
|
||||
|
@ -101,7 +107,7 @@ export default function Nouveautés() {
|
|||
const removeGithubIssuesReferences = (text: string) =>
|
||||
text.replace(/#[0-9]{1,5}/g, '')
|
||||
|
||||
const TextRenderer = ({ children }) => (
|
||||
const TextRenderer = ({ children }: { children: string }) => (
|
||||
<>{emoji(removeGithubIssuesReferences(children))}</>
|
||||
)
|
||||
|
||||
|
|
|
@ -86,8 +86,6 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
|
|||
const parsedRules = useSelector(parsedRulesSelector)
|
||||
const value = useSelector(situationSelector)[dottedName]
|
||||
|
||||
const onChange = x => dispatch(updateSituation(dottedName, x))
|
||||
|
||||
if (!analysis.isApplicable) {
|
||||
return null
|
||||
}
|
||||
|
@ -109,7 +107,7 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
|
|||
dottedName={dottedName}
|
||||
rules={parsedRules}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onChange={x => dispatch(updateSituation(dottedName, x))}
|
||||
useSwitch
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -8,14 +8,15 @@ export const flatActivités = pipe(
|
|||
unnest
|
||||
)(activités)
|
||||
|
||||
export const getActivité = a => flatActivités.find(item => item.titre === a)
|
||||
export const getActivité = (a: string) =>
|
||||
flatActivités.find(item => item.titre === a)
|
||||
|
||||
export const getTranslatedActivité = (title, language) => ({
|
||||
export const getTranslatedActivité = (title: string, language: string) => ({
|
||||
...getActivité(title),
|
||||
...(language !== 'fr' && activitésEn[title])
|
||||
})
|
||||
|
||||
export const getMinimumDéclaration = a => {
|
||||
export const getMinimumDéclaration = (a: string) => {
|
||||
const activité = getActivité(a)
|
||||
if (activité['seuil pro'] === 0 && !activité['seuil régime général']) {
|
||||
return 'RÉGIME_GÉNÉRAL_NON_DISPONIBLE'
|
||||
|
@ -31,7 +32,7 @@ export const getMinimumDéclaration = a => {
|
|||
}
|
||||
return null
|
||||
}
|
||||
export const hasConditions = a => {
|
||||
export const hasConditions = (a: string) => {
|
||||
const activité = getActivité(a)
|
||||
return !!(
|
||||
activité['exonérée sauf si'] ||
|
||||
|
@ -42,5 +43,5 @@ export const hasConditions = a => {
|
|||
)
|
||||
}
|
||||
|
||||
export const getSousActivités = a =>
|
||||
(getActivité(a).activités || []).map(({ titre }) => titre)
|
||||
export const getSousActivités = (a: string) =>
|
||||
(getActivité(a).activités || []).map(({ titre }: { titre: string }) => titre)
|
||||
|
|
|
@ -141,7 +141,7 @@ export const constructLocalizedSitePath = (language: string) => {
|
|||
|
||||
export type SitePathsType = ReturnType<typeof constructLocalizedSitePath>
|
||||
|
||||
const deepReduce = (fn, initialValue?: any, object?: any) =>
|
||||
const deepReduce = (fn: any, initialValue?: any, object?: any): any =>
|
||||
reduce(
|
||||
(acc, [key, value]) =>
|
||||
typeof value === 'object'
|
||||
|
|
|
@ -80,9 +80,19 @@ export default function Studio() {
|
|||
)
|
||||
}
|
||||
|
||||
export const Results = ({ targets, onClickUpdate, onClickShare }) => {
|
||||
type ResultsProps = {
|
||||
targets: string[]
|
||||
onClickUpdate: React.MouseEventHandler
|
||||
onClickShare: React.MouseEventHandler
|
||||
}
|
||||
|
||||
export const Results = ({
|
||||
targets,
|
||||
onClickUpdate,
|
||||
onClickShare
|
||||
}: ResultsProps) => {
|
||||
const [rule, setCurrentTarget] = useState<string>()
|
||||
const currentTarget = rule ?? last(targets)
|
||||
const currentTarget = rule ?? (last(targets) as string)
|
||||
const error = Engine.useError()
|
||||
// EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker
|
||||
// console.warn
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export function capitalise0(name: undefined): undefined
|
||||
export function capitalise0(name: string): string
|
||||
export function capitalise0(name) {
|
||||
export function capitalise0(name?: string) {
|
||||
return name && name[0].toUpperCase() + name.slice(1)
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ export function softCatch<ArgType, ReturnType>(
|
|||
|
||||
export function mapOrApply<A, B>(fn: (a: A) => B, x: A): B
|
||||
export function mapOrApply<A, B>(fn: (a: A) => B, x: Array<A>): Array<B>
|
||||
export function mapOrApply(fn, x) {
|
||||
export function mapOrApply<A, B>(fn: (a: A) => B, x: A | Array<A>) {
|
||||
return Array.isArray(x) ? x.map(fn) : fn(x)
|
||||
}
|
||||
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -1333,7 +1333,7 @@
|
|||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^5.1.0":
|
||||
"@types/react-router-dom@*", "@types/react-router-dom@^5.1.0":
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196"
|
||||
integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA==
|
||||
|
@ -1342,6 +1342,14 @@
|
|||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router-hash-link@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-hash-link/-/react-router-hash-link-1.2.1.tgz#fba7dc351cef2985791023018b7a5dbd0653c843"
|
||||
integrity sha512-jdzPGE8jFGq7fHUpPaKrJvLW1Yhoe5MQCrmgeesC+eSLseMj3cGCTYMDA4BNWG8JQmwO8NTYt/oT3uBZ77pmBA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-router-dom" "*"
|
||||
|
||||
"@types/react-router@*", "@types/react-router@^5.1.2":
|
||||
version "5.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6"
|
||||
|
@ -1350,6 +1358,13 @@
|
|||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-syntax-highlighter@^11.0.4":
|
||||
version "11.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd"
|
||||
integrity sha512-9GfTo3a0PHwQeTVoqs0g5bS28KkSY48pp5659wA+Dp4MqceDEa8EHBqrllJvvtyusszyJhViUEap0FDvlk/9Zg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^16.9.11":
|
||||
version "16.9.23"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.23.tgz#1a66c6d468ba11a8943ad958a8cb3e737568271c"
|
||||
|
|
Loading…
Reference in New Issue