🎨 Ajoute une recherche globale de simulateurs et de règles

Cette recherche se base sur Algolia. Elle est accessible depuis toutes les
pages. Elle remplace intégralement la précédente recherche.

La fonctionnalité possède deux parties:
 - la mise à jour des données (au build)
 - l'UI sous la forme de composants

L'UI se base sur la bibliothèque `react-instantsearch` qui est developpé et
maintenu par Algolia.

Co-authored-by: Johan Girod <johan.girod@beta.gouv.fr>
Co-authored-by: Maxime Quandalle <maxime.quandalle@gmail.com>
Co-authored-by: Alexandre Hajjar <alexandre.hajjar@gmail.com>
pull/1726/head
Alexandre S 2021-06-28 19:30:15 +02:00 committed by Maxime Quandalle
parent 73b3711510
commit 50826d74e4
27 changed files with 1416 additions and 640 deletions

View File

@ -58,10 +58,20 @@ jobs:
env:
AT_INTERNET_SITE_ID: ${{ needs.deploy-context.outputs.env-name == 'master' && 617190 || 617189 }}
NODE_ENV: production
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_SEARCH_KEY: ${{secrets.ALGOLIA_SEARCH_KEY}}
ALGOLIA_INDEX_PREFIX: monentreprise-${{needs.deploy-context.outputs.env-name}}-
- name: Replace site placeholders in netlify.toml redirection file
run: sed -i "s|:SITE_FR|$FR_BASE_URL|g" netlify.toml;
sed -i "s|:SITE_EN|$EN_BASE_URL|g" netlify.toml;
sed -i "s|:SITE_PUBLICODES|$PUBLICODES_BASE_URL|g" netlify.toml
- name: Update Algolia index
run: yarn workspace mon-entreprise algolia:update
env:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_SEARCH_KEY: ${{secrets.ALGOLIA_SEARCH_KEY}}
ALGOLIA_INDEX_PREFIX: monentreprise-${{needs.deploy-context.outputs.env-name}}-
- uses: actions/upload-artifact@v2
with:
name: static-site

View File

@ -31,7 +31,7 @@ Nous utilisons :
### Démarrage
Si possible, assurez-vous d'avoir toutes les clés d'API nécessaires dans votre fichier
`mon-entreprise/.env`.
`mon-entreprise/.env` (un template est disponible dans `mon-entreprise/.env.template`).
**NB : ne vous inquiétez pas, ceci n'est pas nécessaire pour effectuer une première contribution à
la base de code !** Cependant, vous en aurez besoin pour la commande `yarn prepare` et pour les
commandes de traduction automatique français -> anglais. Si vous êtes confronté à ce type de besoin,
@ -173,6 +173,9 @@ Pour tester les règles, il est recommandé de:
- créer des cas de tests de non-régression sous la forme de nouveaux snapshots (cf.
`mon-entreprise/test/regressions`).
En local, le moteur de recherche n'est pas mis à jour automatiquement et la liste des règles
est exposée ici: http://localhost:8080/mon-entreprise/documentation/dev
## Publicodes
### Documentation

View File

@ -0,0 +1,11 @@
ALGOLIA_APP_ID=
ALGOLIA_ADMIN_KEY=
ALGOLIA_SEARCH_KEY=
ALGOLIA_INDEX_PREFIX=monentreprise-{env}-
INSEE_SIRENE_API_SECRET=
MATOMO_TOKEN=
ATINTERNET_API_ACCESS_KEY=
ATINTERNET_API_SECRET_KEY=
DEEPL_API_SECRET=
ZAMMAD_API_SECRET_KEY=
GITHUB_API_SECRET=

View File

@ -0,0 +1,29 @@
const fr = Cypress.env('language') === 'fr'
const simulateursPath = '.ais-Hits-list'
const reglesPath = '.ais-InfiniteHits-list'
describe('Recherche globales', () => {
if (!fr) {
return
}
it('should display the search results when the magnifying glass is clicked', () => {
cy.visit('/')
cy.get('#search-display-button').click()
cy.wait(30)
cy.focused().should('have.attr', 'type', 'search')
cy.wait(100)
cy.get(simulateursPath).children().should('have.length', 6)
cy.get(reglesPath).children().should('have.length', 20)
cy.focused().type('avocat')
cy.wait(100)
cy.get(simulateursPath).children().should('have.length', 1)
cy.get(reglesPath).children().should('have.length', 1)
})
})

View File

@ -67,12 +67,15 @@
"@rehooks/local-storage": "^2.1.1",
"@sentry/react": "^6.3.5",
"@sentry/tracing": "^6.3.5",
"@types/react-instantsearch-dom": "^6.10.1",
"algoliasearch": "^4.10.2",
"classnames": "^2.2.5",
"color-convert": "^1.9.2",
"core-js": "^3.2.1",
"focus-trap-react": "^3.1.2",
"fuse.js": "5.2.1",
"iframe-resizer": "^4.1.1",
"instantsearch.css": "^7.4.5",
"modele-social": "^0.2.0",
"publicodes": "^1.0.0-beta.15",
"publicodes-react": "^1.0.0-beta.15",
@ -83,6 +86,8 @@
"react-easy-emoji": "^1.2.0",
"react-helmet": "^6.1.0",
"react-i18next": "^11.0.0",
"react-instantsearch": "^6.11.2",
"react-instantsearch-dom": "^6.11.2",
"react-markdown": "^4.1.0",
"react-monaco-editor": "^0.40.0",
"react-number-format": "^4.3.1",
@ -128,6 +133,7 @@
"serve:dev": "concurrently -k \"yarn run serve:dev:mon-entreprise\" \"yarn run serve:dev:mycompanyinfrance & yarn run serve:dev:publicodes\"",
"serve:dev:mon-entreprise": "PORT=5000 serve --config serve.mon-entreprise.json --no-clipboard",
"serve:dev:publicodes": "PORT=5002 serve --config serve.publicodes.json --no-clipboard",
"serve:dev:mycompanyinfrance": "PORT=5001 serve --config serve.infrance.json --no-clipboard"
"serve:dev:mycompanyinfrance": "PORT=5001 serve --config serve.infrance.json --no-clipboard",
"algolia:update": "node scripts/search/update-data.js"
}
}

View File

@ -0,0 +1,157 @@
require('dotenv').config()
const algoliasearch = require('algoliasearch')
const rawRules = require('modele-social')
const rules = require('publicodes').parsePublicodes(rawRules)
const getSimulationData = require('../../source/pages/Simulateurs/metadata-src')
const {
ALGOLIA_APP_ID,
ALGOLIA_ADMIN_KEY,
ALGOLIA_INDEX_PREFIX = '',
} = process.env
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_KEY)
const rulesIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}rules`)
const simulateursIndex = client.initIndex(`${ALGOLIA_INDEX_PREFIX}simulateurs`)
const formatRulesToAlgolia = (rules) =>
Object.entries(rules)
.map(([n, rule]) => {
if (!rule) return
const path = n.split(' . ')
const {
title,
rawNode: { icônes = '', description, acronyme, résumé },
} = rule
const ruleName = `${title} ${' ' + icônes}`.trim()
const namespace = path.slice(0, -1)
return {
objectID: n,
path,
ruleName,
namespace,
pathDepth: path.length,
acronyme: acronyme,
titre: title,
icone: icônes,
description: description || résumé,
}
})
.filter(Boolean)
const formatSimulationDataToAlgolia = (simulations) =>
Object.entries(simulations).map(([id, simulation]) => ({
...simulation,
objectID: id,
title: simulation.title || simulation.shortName || simulation.meta.title,
tooltip: simulation.tooltip || '',
description: simulation.meta?.description,
}))
;(async function () {
try {
console.log('Algolia update START')
console.log('Clearing: rules')
await rulesIndex.clearObjects().wait()
console.log('Configure index: rules')
await rulesIndex
.setSettings({
// Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/
minWordSizefor1Typo: 4,
minWordSizefor2Typos: 8,
hitsPerPage: 20,
maxValuesPerFacet: 100,
attributesToIndex: ['unordered(ruleName)', 'unordered(namespace)'],
numericAttributesToIndex: null,
attributesToRetrieve: null,
unretrievableAttributes: null,
optionalWords: null,
attributesForFaceting: null,
attributesToSnippet: null,
attributesToHighlight: ['ruleName', 'namespace'],
paginationLimitedTo: 1000,
attributeForDistinct: null,
exactOnSingleWordQuery: 'attribute',
ranking: [
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom',
],
customRanking: ['asc(pathDepth)'],
separatorsToIndex: '',
removeWordsIfNoResults: 'none',
queryType: 'prefixLast',
highlightPreTag: '<em>',
highlightPostTag: '</em>',
snippetEllipsisText: '',
alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'],
})
.wait()
console.log('Uploading: rules')
await rulesIndex.saveObjects(formatRulesToAlgolia(rules)).wait()
console.log('Clearing: simulateurs')
await simulateursIndex.clearObjects().wait()
console.log('Configure index: simulateurs')
await simulateursIndex
.setSettings({
// Parameters are documented on Algolia website https://www.algolia.com/doc/api-reference/api-parameters/
minWordSizefor1Typo: 4,
minWordSizefor2Typos: 8,
hitsPerPage: 20,
maxValuesPerFacet: 100,
attributesToIndex: [
'unordered(title)',
'unordered(tooltip)',
'unordered(description)',
],
numericAttributesToIndex: null,
attributesToRetrieve: null,
unretrievableAttributes: null,
optionalWords: null,
attributesForFaceting: null,
attributesToSnippet: null,
attributesToHighlight: ['title'],
paginationLimitedTo: 1000,
attributeForDistinct: null,
exactOnSingleWordQuery: 'attribute',
ranking: [
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom',
],
customRanking: null,
separatorsToIndex: '',
removeWordsIfNoResults: 'none',
queryType: 'prefixLast',
highlightPreTag: '<em>',
highlightPostTag: '</em>',
snippetEllipsisText: '',
alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'],
})
.wait()
console.log('Updloading: simulateurs')
await simulateursIndex
.saveObjects(formatSimulationDataToAlgolia(getSimulationData()))
.wait()
console.log('Algolia update DONE')
} catch (e) {
console.log(JSON.stringify(e, null, 2))
}
})()

View File

@ -11,3 +11,18 @@
display: flex;
flex-direction: column;
}
/* Classe pour permettre la présence de labels pour les lecteur d'ecran
* Source: https://a11y-guidelines.orange.com/en/web/components-examples/accessible-hiding/
* */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; /* added line */
border: 0;
}

View File

@ -1,14 +0,0 @@
import SearchBar from 'Components/SearchBar'
import { Trans } from 'react-i18next'
import './RulesList.css'
export default function RulesList() {
return (
<div id="RulesList" className="ui__ container">
<h1>
<Trans>Explorez notre documentation</Trans>
</h1>
<SearchBar />
</div>
)
}

View File

@ -1,8 +0,0 @@
li.active {
background: var(--color);
color: var(--textColor);
}
li.active a {
color: var(--textColor);
}

View File

@ -1,184 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { DottedName } from 'modele-social'
import RuleLink from './RuleLink'
import './SearchBar.css'
import { useEngine } from './utils/EngineContext'
import { utils } from 'publicodes'
// TODO: We should use a normal import here
// We use a dynamic import to work around a typing problem https://github.com/betagouv/mon-entreprise/pull/1616#issuecomment-858629506
let worker: any
;(async function () {
const Worker = ((await import('./SearchBar.worker.js')) as any).default
worker = new Worker()
})()
type SearchBarProps = {
showListByDefault?: boolean
}
type SearchItem = {
title: string
dottedName: DottedName
espace: Array<string>
}
type Matches = Array<{
key: string
value: string
indices: Array<[number, number]>
}>
function highlightMatches(str: string, matches: Matches) {
if (!matches?.length) {
return str
}
const indices = matches[0].indices
.sort(([a], [b]) => a - b)
.map(([x, y]) => [x, y + 1])
.reduce(
(acc, value) =>
acc[acc.length - 1][1] <= value[0] ? [...acc, value] : acc,
[[0, 0]]
)
.flat()
return [...indices, str.length].reduce(
([highlight, prevIndice, acc], currentIndice, i) => {
const currentStr = str.slice(prevIndice, currentIndice)
return [
!highlight,
currentIndice,
[
...acc,
<span
style={highlight ? { fontWeight: 'bold' } : {}}
className={highlight ? 'ui__ light-bg' : ''}
key={i}
>
{currentStr}
</span>,
],
] as [boolean, number, Array<React.ReactNode>]
},
[false, 0, []] as [boolean, number, Array<React.ReactNode>]
)[2]
}
export default function SearchBar({
showListByDefault = false,
}: SearchBarProps) {
const rules = useEngine().getParsedRules()
const [input, setInput] = useState('')
const [results, setResults] = useState<
Array<{
item: SearchItem
matches: Matches
}>
>([])
const { i18n } = useTranslation()
const searchIndex: Array<SearchItem> = useMemo(
() =>
Object.values(rules)
.filter(utils.ruleWithDedicatedDocumentationPage)
.map((rule) => ({
title:
rule.title +
(rule.rawNode.acronyme ? ` (${rule.rawNode.acronyme})` : ''),
dottedName: rule.dottedName,
espace: rule.dottedName.split(' . ').reverse(),
})),
[rules]
)
useEffect(() => {
worker.postMessage({
rules: searchIndex,
})
worker.onmessage = ({ data: results }: any) => setResults(results)
return () => {
worker.onmessage = null
}
}, [searchIndex, setResults])
return (
<>
<input
type="search"
className="ui__"
value={input}
placeholder={i18n.t('Entrez des mots clefs ici')}
onChange={(e) => {
const input = e.target.value
if (input.length > 0) worker.postMessage({ input })
setInput(input)
}}
/>
{!!input.length && !results.length ? (
<p
className="ui__ notice light-bg"
css={`
padding: 0.4rem;
border-radius: 0.3rem;
margin-top: 0.6rem;
`}
>
<Trans i18nKey="noresults">
Aucun résultat ne correspond à cette recherche
</Trans>
</p>
) : (
<ul
css={`
padding: 0;
margin: 0;
list-style: none;
`}
>
{(showListByDefault && !results.length && !input.length
? searchIndex
.filter((item) => item.espace.length === 2)
.map((item) => ({ item, matches: [] }))
: results
)
.slice(0, showListByDefault ? 100 : 6)
.map(({ item, matches }) => (
<li key={item.dottedName}>
<RuleLink
dottedName={item.dottedName}
style={{
width: '100%',
textDecoration: 'none',
lineHeight: '1.5rem',
}}
>
<small>
{item.espace
.slice(1)
.reverse()
.map((name) => (
<span key={name}>
{highlightMatches(
name,
matches.filter(
(m) => m.key === 'espace' && m.value === name
)
)}{' '}
{' '}
</span>
))}
<br />
</small>
{highlightMatches(
item.title,
matches.filter((m) => m.key === 'title')
)}
</RuleLink>
</li>
))}
</ul>
)}
</>
)
}

View File

@ -1,32 +0,0 @@
import Fuse from 'fuse.js'
const searchWeights = [
{
name: 'espace',
weight: 0.6,
},
{
name: 'title',
weight: 0.4,
},
]
let fuse = null
onmessage = function (event) {
if (event.data.rules)
fuse = new Fuse(event.data.rules, {
keys: searchWeights,
includeMatches: true,
minMatchCharLength: 2,
useExtendedSearch: true,
distance: 50,
threshold: 0.3,
})
if (event.data.input) {
let results = fuse
.search(event.data.input + '|' + event.data.input.replace(/ /g, '|'))
.slice()
postMessage(results)
}
}

View File

@ -1,14 +1,25 @@
import { useEffect, useState } from 'react'
import emoji from 'react-easy-emoji'
import { useEffect, useRef, useState } from 'react'
import { Trans } from 'react-i18next'
import { useLocation } from 'react-router'
import styled from 'styled-components'
import Overlay from './Overlay'
import SearchBar from './SearchBar'
import SearchRulesAndSimulators from './search/SearchRulesAndSimulators'
type SearchButtonProps = {
invisibleButton?: boolean
}
const SearchTriggerButton = styled.button`
display: flex;
border: 0px solid;
border-color: rgb(41, 117, 209);
padding: 0.6rem;
font-size: 2rem;
align-items: center;
justify-items: center;
color: rgb(31, 42, 106);
margin: auto;
`
export default function SearchButton({ invisibleButton }: SearchButtonProps) {
export default function SearchButton() {
const { pathname } = useLocation()
const pathnameRef = useRef(pathname)
const [visible, setVisible] = useState(false)
useEffect(() => {
@ -19,7 +30,9 @@ export default function SearchButton({ invisibleButton }: SearchButtonProps) {
e.preventDefault()
return false
}
window.addEventListener('keydown', handleKeyDown)
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
@ -27,19 +40,46 @@ export default function SearchButton({ invisibleButton }: SearchButtonProps) {
const close = () => setVisible(false)
return visible ? (
<Overlay onClose={close}>
<h1>
<Trans>Chercher dans la documentation</Trans>
</h1>
<SearchBar />
</Overlay>
) : invisibleButton ? null : (
<button
className="ui__ simple small button"
onClick={() => setVisible(true)}
>
{emoji('🔍')} <Trans>Rechercher</Trans>
</button>
useEffect(() => {
if (pathname !== pathnameRef.current) {
pathnameRef.current = pathname
close()
}
}, [pathname])
return (
<>
{visible && (
<Overlay onClose={close}>
<h1>
<Trans>Que cherchez-vous ?</Trans>
</h1>
<SearchRulesAndSimulators />
</Overlay>
)}
<SearchTriggerButton
onClick={() => setVisible(true)}
id="search-display-button"
>
<svg
style={{ height: '2rem' }}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="square"
strokeLinejoin="round"
strokeWidth={3}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<div className="sr-only">
<Trans>Rechercher</Trans>
</div>
</SearchTriggerButton>
</>
)
}

View File

@ -3,7 +3,6 @@ import Conversation, {
} from 'Components/conversation/Conversation'
import ExportSimulationBanner from 'Components/ExportSimulationBanner'
import PageFeedback from 'Components/Feedback'
import SearchButton from 'Components/SearchButton'
import ShareSimulationBanner from 'Components/ShareSimulationBanner'
import TargetSelection from 'Components/TargetSelection'
import Progress from 'Components/ui/Progress'
@ -42,7 +41,6 @@ export default function Simulation({
<ExportRecover />
{simulationBloc}
<SearchButton invisibleButton />
{!firstStepCompleted && <TrackPage name="accueil" />}
{firstStepCompleted && (
<>

View File

@ -7,6 +7,7 @@ import { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import NewsBanner from './NewsBanner'
import SearchButton from 'Components/SearchButton'
export default function Header() {
const sitePaths = useContext(SitePathsContext)
@ -56,6 +57,7 @@ export default function Header() {
>
<img alt="logo urssaf" style={{ height: '100%' }} src={urssafSvg} />
</a>
{language === 'fr' && <SearchButton />}
</div>
<NewsBanner />
</>

View File

@ -0,0 +1,70 @@
import { Trans, useTranslation } from 'react-i18next'
import {
connectInfiniteHits,
connectStats,
Highlight,
} from 'react-instantsearch-dom'
import { Names } from '../../../../modele-social/dist/names'
import RuleLink from '../RuleLink'
import { Hit as AlgoliaHit } from 'react-instantsearch-core'
type Hit = AlgoliaHit<{ objectID: Names; namespace?: string }>
const Hit = (hit: Hit) => {
return (
<RuleLink dottedName={hit.objectID} className="hit-content">
{hit.namespace && (
<div className="hit-amespace ui__ notice">
<Highlight hit={hit} attribute="namespace" separator=" > " />
</div>
)}
<div className="hit-ruleName">
<Highlight hit={hit} attribute="ruleName" />
</div>
</RuleLink>
)
}
const HideableTitle = connectStats(({ nbHits }) => {
return nbHits === 0 ? (
<></>
) : (
<h2>
<Trans>Règles de calculs</Trans>
</h2>
)
})
const Hits = connectInfiniteHits(({ hits, hasMore, refineNext }) => {
const { t } = useTranslation()
return (
<div className="hit-container">
<div className="ais-InfiniteHits">
<>
<ol className="ais-InfiniteHits-list">
{hits.map((hit) => (
<li className="ais-InfiniteHits-item" key={hit.objectID}>
<Hit {...hit} />
</li>
))}
</ol>
{hasMore && (
<button className="ais-InfiniteHits-loadMore" onClick={refineNext}>
{t('Charger plus de résultats')}
</button>
)}
</>
</div>
</div>
)
})
export const RulesInfiniteHits = () => {
return (
<>
<HideableTitle />
<Hits />
</>
)
}

View File

@ -0,0 +1,36 @@
import '../ui/Card.css'
import './SearchRulesAndSimulators.css'
import 'instantsearch.css/themes/satellite.css'
import algoliasearch from 'algoliasearch/lite'
import { Trans, useTranslation } from 'react-i18next'
import { InstantSearch, SearchBox } from 'react-instantsearch-dom'
import { RulesInfiniteHits } from './RulesInfiniteHits'
const ALGOLIA_APP_ID = process.env.ALGOLIA_APP_ID || ''
const ALGOLIA_SEARCH_KEY = process.env.ALGOLIA_SEARCH_KEY || ''
const ALGOLIA_INDEX_PREFIX = process.env.ALGOLIA_INDEX_PREFIX || ''
const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY)
export default function SearchRules() {
const { t } = useTranslation()
return (
<InstantSearch
indexName={`${ALGOLIA_INDEX_PREFIX}rules`}
searchClient={searchClient}
>
<SearchBox
translations={{
submitTitle: t('Valider votre recherche'),
resetTitle: t('Réinitialiser votre recherche'),
placeholder: t('Cherchez par mot-clef ou acronyme...'),
}}
/>
<h2>
<Trans>Règles de calculs</Trans>
</h2>
<RulesInfiniteHits />
</InstantSearch>
)
}

View File

@ -0,0 +1,57 @@
.ais-InfiniteHits {
}
.ais-InfiniteHits-list > .ais-InfiniteHits-item {
padding: 0;
}
.ais-InfiniteHits-list > .ais-InfiniteHits-item > .hit-content {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 0.5rem;
text-decoration: none;
}
.ais-InfiniteHits-list > .ais-InfiniteHits-item > .hit-content > .hit-ruleName {
text-decoration: underline;
}
.ais-InfiniteHits-list
> .ais-InfiniteHits-item
> .hit-content
> .ais-Highlight {
display: block;
flex-grow: 1;
}
.ais-Hits-list {
display: grid;
grid-column-gap: 0.5rem;
grid-row-gap: 0.7rem;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 1fr;
}
.ais-Hits-list > .box {
padding: 0.5em;
}
.simulator-hit-content {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 !important;
}
.simulator-hit-content .ais-Highlight {
font-size: 0.9em;
}
.simulator-icon {
height: 2em;
}

View File

@ -0,0 +1,47 @@
import '../ui/Card.css'
import './SearchRulesAndSimulators.css'
import 'instantsearch.css/themes/satellite.css'
import algoliasearch from 'algoliasearch/lite'
import { useTranslation } from 'react-i18next'
import {
Configure,
Index,
InstantSearch,
SearchBox,
} from 'react-instantsearch-dom'
import { SimulatorHits } from './SimulatorHits'
import { RulesInfiniteHits } from './RulesInfiniteHits'
const ALGOLIA_APP_ID = process.env.ALGOLIA_APP_ID || ''
const ALGOLIA_SEARCH_KEY = process.env.ALGOLIA_SEARCH_KEY || ''
const ALGOLIA_INDEX_PREFIX = process.env.ALGOLIA_INDEX_PREFIX || ''
const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY)
export default function SearchRulesAndSimulators() {
const { t } = useTranslation()
return (
<InstantSearch
indexName={`${ALGOLIA_INDEX_PREFIX}rules`}
searchClient={searchClient}
>
<SearchBox
translations={{
submitTitle: t('Valider votre recherche'),
resetTitle: t('Réinitialiser votre recherche'),
placeholder: t('Cherchez par mot-clef ou accronyme...'),
}}
/>
<Index indexName={`${ALGOLIA_INDEX_PREFIX}simulateurs`}>
<Configure hitsPerPage={6} />
<SimulatorHits />
</Index>
<Index indexName={`${ALGOLIA_INDEX_PREFIX}rules`}>
<RulesInfiniteHits />
</Index>
</InstantSearch>
)
}

View File

@ -0,0 +1,57 @@
import Emoji from 'Components/utils/Emoji'
import { SitePathsContext } from 'Components/utils/SitePathsContext'
import { path } from 'ramda'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { connectHits, Highlight } from 'react-instantsearch-dom'
import { Link } from 'react-router-dom'
type AlgoliaSimulatorHit = {
objectID: string
icône: string
title: string
pathId: string
}
type SimulatorHitProps = {
hit: AlgoliaSimulatorHit
path?: string
}
const SimulatorHit = ({ hit, path = '' }: SimulatorHitProps) => (
<Link
className="simulator-hit-content ui__ interactive card box"
to={path || ''}
>
<div className="ui__ box-icon">
{hit.icône && <Emoji emoji={hit.icône} />}{' '}
</div>
<Highlight hit={hit} attribute="title" />
</Link>
)
type SimulatorHitsProps = {
hits: Array<AlgoliaSimulatorHit>
}
export const SimulatorHits = connectHits(({ hits }: SimulatorHitsProps) => {
const sitePaths = useContext(SitePathsContext)
return (
<>
{hits.length > 0 && (
<h2>
<Trans>Simulateurs</Trans>
</h2>
)}
<div className="ais-Hits-list">
{hits.map((hit) => (
<SimulatorHit
key={hit.objectID}
hit={hit}
path={path(hit.pathId.split('.'), sitePaths)}
/>
))}
</div>
</>
)
})

View File

@ -0,0 +1,7 @@
type Screen = 'phone' | 'tablet' | 'desktop'
export const breakpoints: Record<Screen, string> = {
phone: '0px',
tablet: '850px',
desktop: '1200px',
}

View File

@ -139,6 +139,7 @@ Professions libérales: By job
Protection sociale: Social security
Précédent: Previous
Prévisualisation: Preview
Que cherchez-vous ?: What are you looking for?
Quel module ?: What module?
Quelle couleur ?: What color?
Quelques exemples de salaires: Some salary exemples
@ -161,6 +162,7 @@ Revenu du dirigeant par statut: Executive income by status
"Revenu net avec chômage partiel :": "Net income with short-time work :"
Revenu net mensuel: Monthly net income
Revenus étranger: Foreign income
Règles de calculs: Rules
Réductions: Discounts
Rémunération du dirigeant: Director's remuneration
Répartition du chiffre d'affaires: Breakdown of turnover
@ -176,6 +178,7 @@ Salariés et employeurs: Employees and employers
Sans responsabilité limitée: Without limited liability
Si: If
Simulateur de salaire: Employee salary simulation
Simulateurs: Simulations
Simulations personnalisées: Customized simulations
Sinon: Else
Situation personnelle: Personal situation

View File

@ -62,6 +62,7 @@ Prochaines questions: Prochaines questions
Professions libérales: Professions libérales
Précédent: Précédent
Prévisualisation: Prévisualisation
Que cherchez-vous ?: Que cherchez-vous ?
Quelques intégrations: Quelques intégrations
Rechercher: Rechercher
Ressources utiles: Ressources utiles
@ -70,11 +71,13 @@ Retour à la création: Retour à la création
Retour à mon activité: Retour à mon activité
Revenu du dirigeant par statut: Revenu du dirigeant par statut
Revenus étranger: Revenus étranger
Règles de calculs: Règles de calculs
Répartition du chiffre d'affaires: Répartition du chiffre d'affaires
S'inscrire: S'inscrire
Salaire: Salaire
Salaire net: Salaire net
Salariés et employeurs: Salariés et employeurs
Simulateurs: Simulateurs
Situation personnelle: Situation personnelle
Suivant: Suivant
Total des retenues: Total des retenues

View File

@ -1,5 +1,4 @@
import SearchBar from 'Components/SearchBar'
import SearchButton from 'Components/SearchButton'
import SearchRules from 'Components/search/SearchRules'
import { FromBottom } from 'Components/ui/animate'
import { ThemeColorsProvider } from 'Components/utils/colors'
import { useEngine } from 'Components/utils/EngineContext'
@ -12,6 +11,8 @@ import { useSelector } from 'react-redux'
import { Redirect, useHistory, useLocation } from 'react-router-dom'
import { RootState } from 'Reducers/rootReducer'
import { TrackPage } from '../ATInternetTracking'
import rules, { DottedName } from 'modele-social'
import RuleLink from '../components/RuleLink'
export default function RulePage() {
const currentSimulation = useSelector(
@ -32,9 +33,15 @@ export default function RulePage() {
if (pathname === '/documentation') {
return <DocumentationLanding />
}
if (pathname === '/documentation/dev') {
return <DocumentationRulesList />
}
if (!documentationSitePaths[pathname]) {
return <Redirect to="/404" />
}
return (
<FromBottom>
<TrackPage
@ -51,7 +58,6 @@ export default function RulePage() {
`}
>
{currentSimulation ? <BackToSimulation /> : <span />}
<SearchButton key={pathname} />
</div>
<Documentation
language={i18n.language as 'fr' | 'en'}
@ -88,7 +94,21 @@ function DocumentationLanding() {
<Trans i18nKey="page.documentation.title">Documentation</Trans>
</h1>
<p>Explorez toutes les règles de la documentation</p>
<SearchBar showListByDefault={true} />
<SearchRules />
</>
)
}
function DocumentationRulesList() {
const ruleEntries = Object.keys(rules) as DottedName[]
return (
<>
<h1>Liste des règles</h1>
{ruleEntries.map((name) => (
<RuleLink dottedName={name} key={name}>
{name}
</RuleLink>
))}
</>
)
}

View File

@ -0,0 +1,567 @@
/**
* Contient l'intégralité des données concernant les différents simulateurs
* sans dépendance qui compliquerait leur import dans le script de mise à jour
* des données pour Algolia.
*/
module.exports = ({ t = (_, text) => text } = {}) => {
return {
salarié: {
tracking: 'salarie',
icône: '🤝',
title: t(
'pages.simulateurs.salarié.title',
'Simulateur de revenus pour salarié'
),
iframePath: 'simulateur-embauche',
meta: {
description: t(
'pages.simulateurs.salarié.meta.description',
"Calcul du salaire net, net après impôt et coût total employeur. Beaucoup d'options disponibles (cadre, stage, apprentissage, heures supplémentaires, etc.)"
),
ogDescription: t(
'pages.simulateurs.salarié.meta.ogDescription',
"En tant que salarié, calculez immédiatement votre revenu net après impôt à partir du brut mensuel ou annuel. En tant qu'employé, estimez le coût total d'une embauche à partir du brut. Ce simulateur est développé avec les experts de l'Urssaf, et il adapte les calculs à votre situation (statut cadre, stage, apprentissage, heures supplémentaire, titre-restaurants, mutuelle, temps partiel, convention collective, etc.)"
),
ogTitle: t(
'pages.simulateurs.salarié.meta.ogTitle',
'Salaire brut, net, net après impôt, coût total : le simulateur ultime pour salariés et employeurs'
),
title: t(
'pages.simulateurs.salarié.meta.titre',
'Salaire brut / net : le convertisseur Urssaf'
),
},
pathId: 'simulateurs.salarié',
shortName: t('pages.simulateurs.salarié.shortname', 'Salarié'),
nextSteps: ['chômage-partiel', 'aides-embauche'],
},
'entreprise-individuelle': {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EI',
},
iframePath: 'simulateur-EI',
icône: '🚶‍♀️',
meta: {
description: t(
'pages.simulateurs.ei.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
),
ogDescription: t(
'pages.simulateurs.ei.meta.ogDescription',
"Grâce au simulateur de revenu pour entreprise individuelle développé par l'Urssaf, vous pourrez estimer le montant de vos revenus en fonction de votre chiffre d'affaires mensuel ou annuel pour mieux gérer votre trésorerie. Ou dans le sens inverse : savoir quel montant facturer pour atteindre un certain revenu."
),
ogTitle: t(
'pages.simulateurs.ei.meta.ogTitle',
'Entreprise individuelle (EI) : calculez rapidement votre revenu net à partir du CA et vice-versa'
),
title: t(
'pages.simulateurs.ei.meta.titre',
'Entreprise individuelle (EI) : simulateur de revenus'
),
},
pathId: 'simulateurs.entreprise-individuelle',
shortName: t('pages.simulateurs.ei.shortname', 'EI'),
title: t(
'pages.simulateurs.ei.title',
'Simulateur pour entreprise individuelle (EI)'
),
nextSteps: ['comparaison-statuts'],
},
eirl: {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EIRL',
},
icône: '🚶',
iframePath: 'simulateur-EIRL',
meta: {
description: t(
'pages.simulateurs.eirl.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
),
ogDescription: t(
'pages.simulateurs.eirl.meta.ogDescription',
"Grâce au simulateur de revenu pour EIRL développé par l'Urssaf, vous pourrez estimer le montant de vos revenus en fonction de votre chiffre d'affaires mensuel ou annuel pour mieux gérer votre trésorerie. Ou dans le sens inverse : savoir quel montant facturer pour atteindre un certain revenu."
),
ogTitle: t(
'pages.simulateurs.eirl.meta.ogTitle',
"Dirigeant d'EIRL : calculez rapidement votre revenu net à partir du CA et vice-versa"
),
title: t(
'pages.simulateurs.eirl.meta.titre',
'EIRL : simulateur de revenus pour dirigeant'
),
},
pathId: 'simulateurs.eirl',
shortName: t('pages.simulateurs.eirl.shortname', 'EIRL'),
title: t('pages.simulateurs.eirl.title', "Simulateur d'EIRL"),
nextSteps: ['comparaison-statuts'],
},
sasu: {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'SASU',
},
icône: '📘',
iframePath: 'simulateur-assimilesalarie',
meta: {
description: t(
'pages.simulateurs.sasu.meta.description',
'Calcul du salaire net à partir du total alloué à la rémunération et inversement'
),
ogDescription: t(
'pages.simulateurs.sasu.meta.ogDescription',
'En tant que dirigeant assimilé-salarié, calculez immédiatement votre revenu net après impôt à partir du total alloué à votre rémunération.'
),
ogTitle: t(
'pages.simulateurs.sasu.meta.ogTitle',
'Rémunération du dirigeant de SASU : un simulateur pour connaître votre salaire net'
),
title: t(
'pages.simulateurs.sasu.meta.titre',
'SASU : simulateur de revenus pour dirigeant'
),
},
pathId: 'simulateurs.sasu',
shortName: t('pages.simulateurs.sasu.shortname', 'SASU'),
title: t('pages.simulateurs.sasu.title', 'Simulateur de SASU'),
nextSteps: ['is', 'comparaison-statuts'],
},
eurl: {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EURL',
},
icône: '📕',
iframePath: 'simulateur-eurl',
meta: {
description: t(
'pages.simulateurs.eurl.meta.description',
'Calcul du salaire net à partir du total alloué à la rémunération et inversement'
),
ogDescription: t(
'pages.simulateurs.eurl.meta.ogDescription',
'En tant que dirigeant assimilé-salarié, calculez immédiatement votre revenu net après impôt à partir du total alloué à votre rémunération.'
),
ogTitle: t(
'pages.simulateurs.eurl.meta.ogTitle',
"Rémunération du dirigeant d'EURL : un simulateur pour connaître votre salaire net"
),
title: t(
'pages.simulateurs.eurl.meta.titre',
'EURL : simulateur de revenus pour dirigeant'
),
},
pathId: 'simulateurs.eurl',
shortName: t('pages.simulateurs.sasu.shortname', 'EURL'),
title: t('pages.simulateurs.sasu.title', "Simulateur d'EURL"),
nextSteps: ['is', 'comparaison-statuts'],
},
'auto-entrepreneur': {
tracking: 'auto_entrepreneur',
icône: '🚶‍♂️',
iframePath: 'simulateur-autoentrepreneur',
meta: {
description: t(
'pages.simulateurs.auto-entrepreneur.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
),
ogDescription: t(
'pages.simulateurs.auto-entrepreneur.meta.ogDescription',
"Grâce au simulateur de revenu auto-entrepreneur développé par l'Urssaf, vous pourrez estimer le montant de vos revenus en fonction de votre chiffre d'affaires mensuel ou annuel pour mieux gérer votre trésorerie. Ou dans le sens inverse : savoir quel montant facturer pour atteindre un certain revenu."
),
ogTitle: t(
'pages.simulateurs.auto-entrepreneur.meta.ogTitle',
'Auto-entrepreneur : calculez rapidement votre revenu net à partir du CA et vice-versa'
),
title: t(
'pages.simulateurs.auto-entrepreneur.meta.titre',
'Auto-entrepreneurs : simulateur de revenus'
),
},
pathId: 'simulateurs.auto-entrepreneur',
shortName: t(
'pages.simulateurs.auto-entrepreneur.shortname',
'Auto-entrepreneur'
),
title: t(
'pages.simulateurs.auto-entrepreneur.title',
'Simulateur de revenus auto-entrepreneur'
),
nextSteps: ['indépendant', 'comparaison-statuts'],
},
indépendant: {
tracking: 'independant',
icône: '🏃',
iframePath: 'simulateur-independant',
pathId: 'simulateurs.indépendant',
shortName: t('pages.simulateurs.indépendant.shortname', 'Indépendant'),
title: t(
'pages.simulateurs.indépendant.title',
'Simulateur de revenus pour indépendant'
),
meta: {
title: t(
'pages.simulateurs.indépendant.meta.title',
'Indépendant : simulateur de revenus'
),
description: t(
'pages.simulateurs.indépendant.meta.description',
"Calcul du revenu net après impôt et des cotisations à partir du chiffre d'affaires et inversement"
),
},
nextSteps: ['comparaison-statuts', 'is'],
},
'artiste-auteur': {
icône: '👩‍🎨',
tracking: 'artiste-auteur',
iframePath: 'simulateur-artiste-auteur',
meta: {
title: t(
'pages.simulateurs.artiste-auteur.meta.title',
'Artiste-auteur: calcul des cotisations Urssaf'
),
description: t(
'pages.simulateurs.artiste-auteur.meta.description',
"Estimez les cotisations sociales sur les droits d'auteur et sur le revenu BNC"
),
ogTitle: 'Artiste-auteur : estimez vos cotisations Urssaf',
ogDescription:
"Renseignez vos revenus (droits d'auteur et bnc) et découvrez immédiatement le montant des cotisations que vous aurez à payer sur l'année.",
},
pathId: 'simulateurs.artiste-auteur',
title: t(
'pages.simulateurs.artiste-auteur.title',
'Estimer mes cotisations dartiste-auteur'
),
shortName: t(
'pages.simulateurs.artiste-auteur.shortname',
'Artiste-auteur'
),
},
'chômage-partiel': {
tracking: 'chomage_partiel',
pathId: 'simulateurs.chômage-partiel',
icône: '😷',
iframePath: 'simulateur-chomage-partiel',
meta: {
description: t(
'pages.simulateurs.chômage-partiel.meta.description',
"Calcul du revenu net pour l'employé et du reste à charge pour l'employeur après remboursement de l'Etat, en prenant en compte toutes les cotisations sociales."
),
ogDescription: t(
'pages.simulateurs.chômage-partiel.meta.ogDescription',
"Accédez à une première estimation en saisissant à partir d'un salaire brut. Vous pourrez ensuite personaliser votre situation (temps partiel, convention, etc). Prends en compte la totalité des cotisations, y compris celles spécifiques à l'indemnité (CSG et CRDS)."
),
ogTitle: t(
'pages.simulateurs.chômage-partiel.meta.ogTitle',
"Simulateur chômage partiel : découvrez l'impact sur le revenu net salarié et le coût total employeur."
),
title: t(
'pages.simulateurs.chômage-partiel.meta.titre',
"Calcul de l'indemnité chômage partiel : le simulateur Urssaf"
),
},
shortName: t(
'pages.simulateurs.chômage-partiel.shortname',
'Chômage partiel'
),
title: t(
'pages.simulateurs.chômage-partiel.title',
'Covid-19 : Simulateur de chômage partiel'
),
nextSteps: ['salarié', 'aides-embauche'],
},
'comparaison-statuts': {
tracking: 'comparaison_statut',
icône: '📊',
pathId: 'simulateurs.comparaison',
title: t(
'pages.simulateurs.comparaison.title',
'Indépendant, assimilé salarié ou auto-entrepreneur : quel régime choisir ?'
),
meta: {
description: t(
'pages.simulateurs.comparaison.meta.description',
'Auto-entrepreneur, indépendant ou dirigeant de SASU ? Avec ce comparatif, trouvez le régime qui vous correspond le mieux'
),
title: t(
'pages.simulateurs.comparaison.meta.title',
"Création d'entreprise : le comparatif des régimes sociaux"
),
},
shortName: t(
'pages.simulateurs.comparaison.shortname',
'Comparaison des statuts'
),
},
'économie-collaborative': {
tracking: 'economie_collaborative',
meta: {
title: t(
'pages.économie-collaborative.meta.title',
'Déclaration des revenus des plateforme en ligne : guide intéractif'
),
description: t(
'pages.économie-collaborative.meta.description',
'Airbnb, Drivy, Blablacar, Leboncoin... Découvrez comment être en règle dans vos déclarations'
),
},
icône: '🙋',
pathId: 'simulateurs.économieCollaborative.index',
shortName: t(
'pages.économie-collaborative.shortname',
'Guide économie collaborative'
),
},
'aide-déclaration-indépendant': {
tracking: {
chapter1: 'gerer',
chapter2: 'aide_declaration_independant',
},
icône: '✍️',
meta: {
description: t(
'pages.gérer.aide-déclaration-indépendant.meta.description',
'Calculer facilement les montants des charges sociales à reporter dans votre déclaration de revenu 2020.'
),
title: t(
'pages.gérer.aide-déclaration-indépendant.meta.title',
'Déclaration de revenus indépendant : calcul du montant des cotisations'
),
},
pathId: 'simulateurs.déclarationIndépendant',
shortName: t(
'pages.gérer.aide-déclaration-indépendant.shortname',
'Aide à la déclaration de revenu'
),
title: t(
'pages.gérer.aide-déclaration-indépendant.title',
"Aide à la déclaration de revenus au titre de l'année 2020"
),
},
'demande-mobilité': {
tracking: {
chapter1: 'gerer',
chapter2: 'demande_mobilite',
},
icône: '🧳',
meta: {
title: t(
'pages.gérer.demande-mobilité.meta.title',
'Travailleur indépendant : demande de mobilité en Europe'
),
description: t(
'pages.gérer.demande-mobilité.meta.description',
"Formulaire interactif à compléter pour les indépendants souhaitant exercer leur activité dans d'autres pays d'Europe"
),
},
pathId: 'gérer.formulaireMobilité',
shortName: t(
'pages.gérer.demande-mobilité.shortname',
'Demande de mobilité internationale'
),
private: true,
iframePath: 'demande-mobilite',
},
médecin: {
tracking: {
chapter2: 'profession_liberale',
chapter3: 'medecin',
},
icône: '⚕️',
iframePath: 'médecin',
pathId: 'simulateurs.profession-libérale.médecin',
shortName: t('pages.simulateurs.médecin.shortname', 'Médecin'),
title: t(
'pages.simulateurs.médecin.title',
'Simulateur de revenus pour médecin en libéral'
),
},
'chirurgien-dentiste': {
icône: '🦷',
tracking: {
chapter2: 'profession_liberale',
chapter3: 'chirurgien_dentiste',
},
iframePath: 'chirurgien-dentiste',
pathId: 'simulateurs.profession-libérale.chirurgien-dentiste',
shortName: t(
'pages.simulateurs.chirurgien-dentiste.shortname',
'Chirurgien-dentiste'
),
title: t(
'pages.simulateurs.chirurgien-dentiste.title',
'Simulateur de revenus pour chirurgien-dentiste en libéral'
),
},
'sage-femme': {
icône: '👶',
tracking: {
chapter2: 'profession_liberale',
chapter3: 'sage_femme',
},
iframePath: 'sage-femme',
pathId: 'simulateurs.profession-libérale.sage-femme',
shortName: t('pages.simulateurs.sage-femme.shortname', 'Sage-femme'),
title: t(
'pages.simulateurs.sage-femme.title',
'Simulateur de revenus pour sage-femme en libéral'
),
},
'auxiliaire-médical': {
tracking: {
chapter2: 'profession_liberale',
chapter3: 'auxiliaire_medical',
},
tooltip: t(
'pages.simulateurs.auxiliaire.tooltip',
'Infirmiers, masseurs-kinésithérapeutes, pédicures-podologues, orthophonistes et orthoptistes'
),
icône: '🩹',
iframePath: 'auxiliaire-medical',
pathId: 'simulateurs.profession-libérale.auxiliaire',
shortName: t('pages.simulateurs.auxiliaire.shortname', 'Auxiliaire méd.'),
title: t(
'pages.simulateurs.auxiliaire.title',
'Simulateur de revenus pour auxiliaire médical en libéral'
),
},
avocat: {
tracking: {
chapter2: 'profession_liberale',
chapter3: 'avocat',
},
icône: '⚖', // j'ai hesité avec 🥑 mais pas envie de me prendre un procès
iframePath: 'avocat',
pathId: 'simulateurs.profession-libérale.avocat',
shortName: t('pages.simulateurs.avocat.shortname', 'Avocat'),
title: t(
'pages.simulateurs.avocat.title',
'Simulateur de revenus pour avocat en libéral'
),
},
'expert-comptable': {
tracking: {
chapter2: 'profession_liberale',
chapter3: 'expert_comptable',
},
icône: '🧮',
iframePath: 'expert-comptable',
pathId: 'simulateurs.profession-libérale.expert-comptable',
shortName: t(
'pages.simulateurs.expert-comptable.shortname',
'Expert-Comptable'
),
title: t(
'pages.simulateurs.expert-comptable.title',
'Simulateur de revenus pour expert comptable et commissaire aux comptes en libéral'
),
},
'profession-libérale': {
tracking: {
chapter2: 'profession_liberale',
},
icône: '💻',
meta: {
title: t(
'pages.simulateurs.profession-libérale.meta.title',
'Professions libérale : le simulateur Urssaf'
),
description: t(
'pages.simulateurs.profession-libérale.meta.description',
"Calcul du revenu net pour les indépendants en libéral à l'impôt sur le revenu (IR, BNC)"
),
},
iframePath: 'profession-liberale',
pathId: 'simulateurs.profession-libérale.index',
shortName: t(
'pages.simulateurs.profession-libérale.shortname',
'Profession libérale'
),
title: t(
'pages.simulateurs.profession-libérale.title',
'Simulateur de revenus pour profession libérale'
),
},
pamc: {
private: true,
iframePath: 'pamc',
tracking: {},
title: t(
'pages.simulateurs.pamc.title',
'PAMC : simulateurs de cotisations et de revenu'
),
pathId: 'simulateurs.pamc',
icône: '🏥',
meta: {
title: t(
'pages.simulateurs.pamc.meta.title',
'Simulateurs régime PAMC'
),
description: t(
'pages.simulateurs.pamc.meta.description',
'Calcul du revenu net pour les professions libérales du régime PAMC (médecin, chirurgien-dentiste, sage-femme et auxiliaire médical)'
),
},
shortName: t('pages.simulateurs.pamc.shortname', 'PAMC'),
},
'aides-embauche': {
icône: '🎁',
tracking: 'aides_embauche',
meta: {
title: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
description: t(
'pages.simulateurs.aides-embauche.meta.description',
'Découvrez les principales aides à lembauche et estimez leur montant en répondant à quelques questions.'
),
color: '#11965f',
},
pathId: 'simulateurs.aide-embauche',
iframePath: 'aides-embauche',
shortName: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
title: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
description: t(
'pages.simulateurs.aides-embauche.introduction',
"Les employeurs peuvent bénéficier d'une aide financière pour l'embauche de certains publics prioritaires. Découvrez les dispositifs existants et estimez le montant de l'aide en répondant aux questions."
),
nextSteps: ['salarié'],
},
is: {
icône: '🗓',
tracking: 'impot-societe',
pathId: 'simulateurs.is',
iframePath: 'impot-societe',
meta: {
title: t('pages.simulateurs.is.meta.title', 'Impôt sur les sociétés'),
description: t(
'pages.simulateurs.is.meta.description',
'Calculez votre impôt sur les sociétés'
),
color: '#E71D66',
},
shortName: t('pages.simulateurs.is.meta.title', 'Impôt sur les sociétés'),
title: t(
'pages.simulateurs.is.title',
"Simulateur d'impôt sur les sociétés"
),
nextSteps: ['salarié', 'comparaison-statuts'],
},
}
}

View File

@ -39,6 +39,7 @@ import PAMCHome from './PAMCHome'
import SalariéSimulation from './Salarié'
import SchemeComparaisonPage from './SchemeComparaison'
import ÉconomieCollaborative from './ÉconomieCollaborative'
import getData from './metadata-src.js'
const simulateurs = [
'salarié',
@ -106,40 +107,19 @@ export function getSimulatorsData({
t = (_: unknown, text: string) => text,
sitePaths = constructLocalizedSitePath('fr'),
language = 'fr',
}): SimulatorData {
} = {}): SimulatorData {
const pureSimulatorsData = getData({ t })
return {
salarié: {
tracking: 'salarie',
...pureSimulatorsData['salarié'],
config: salariéConfig,
component: SalariéSimulation,
icône: '🤝',
title: t(
'pages.simulateurs.salarié.title',
'Simulateur de revenus pour salarié'
),
iframePath: 'simulateur-embauche',
meta: {
description: t(
'pages.simulateurs.salarié.meta.description',
"Calcul du salaire net, net après impôt et coût total employeur. Beaucoup d'options disponibles (cadre, stage, apprentissage, heures supplémentaires, etc.)"
),
ogDescription: t(
'pages.simulateurs.salarié.meta.ogDescription',
"En tant que salarié, calculez immédiatement votre revenu net après impôt à partir du brut mensuel ou annuel. En tant qu'employé, estimez le coût total d'une embauche à partir du brut. Ce simulateur est développé avec les experts de l'Urssaf, et il adapte les calculs à votre situation (statut cadre, stage, apprentissage, heures supplémentaire, titre-restaurants, mutuelle, temps partiel, convention collective, etc.)"
),
...pureSimulatorsData['salarié'].meta,
ogImage:
language === 'fr' ? salaireBrutNetPreviewFR : salaireBrutNetPreviewEN,
ogTitle: t(
'pages.simulateurs.salarié.meta.ogTitle',
'Salaire brut, net, net après impôt, coût total : le simulateur ultime pour salariés et employeurs'
),
title: t(
'pages.simulateurs.salarié.meta.titre',
'Salaire brut / net : le convertisseur Urssaf'
),
},
path: sitePaths.simulateurs.salarié,
shortName: t('pages.simulateurs.salarié.shortname', 'Salarié'),
seoExplanations: (
<Trans i18nKey="pages.simulateurs.salarié.seo">
<h2>Comment calculer le salaire net ?</h2>
@ -215,10 +195,7 @@ export function getSimulatorsData({
nextSteps: ['chômage-partiel', 'aides-embauche'],
},
'entreprise-individuelle': {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EI',
},
...pureSimulatorsData['entreprise-individuelle'],
config: {
...indépendantConfig,
situation: {
@ -226,34 +203,12 @@ export function getSimulatorsData({
'entreprise . imposition': "'IR'",
},
},
iframePath: 'simulateur-EI',
icône: '🚶‍♀️',
meta: {
description: t(
'pages.simulateurs.ei.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
),
ogDescription: t(
'pages.simulateurs.ei.meta.ogDescription',
"Grâce au simulateur de revenu pour entreprise individuelle développé par l'Urssaf, vous pourrez estimer le montant de vos revenus en fonction de votre chiffre d'affaires mensuel ou annuel pour mieux gérer votre trésorerie. Ou dans le sens inverse : savoir quel montant facturer pour atteindre un certain revenu."
),
...pureSimulatorsData['entreprise-individuelle'].meta,
ogImage: AutoEntrepreneurPreview,
ogTitle: t(
'pages.simulateurs.ei.meta.ogTitle',
'Entreprise individuelle (EI) : calculez rapidement votre revenu net à partir du CA et vice-versa'
),
title: t(
'pages.simulateurs.ei.meta.titre',
'Entreprise individuelle (EI) : simulateur de revenus'
),
},
component: EntrepriseIndividuelle,
path: sitePaths.simulateurs['entreprise-individuelle'],
shortName: t('pages.simulateurs.ei.shortname', 'EI'),
title: t(
'pages.simulateurs.ei.title',
'Simulateur pour entreprise individuelle (EI)'
),
seoExplanations: (
<Trans i18nKey="pages.simulateurs.ei.seo explanation">
<h2>
@ -322,69 +277,24 @@ export function getSimulatorsData({
nextSteps: ['comparaison-statuts'],
},
eirl: {
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EIRL',
},
...pureSimulatorsData['eirl'],
config: indépendantConfig,
icône: '🚶',
iframePath: 'simulateur-EIRL',
meta: {
description: t(
'pages.simulateurs.eirl.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
),
ogDescription: t(
'pages.simulateurs.eirl.meta.ogDescription',
"Grâce au simulateur de revenu pour EIRL développé par l'Urssaf, vous pourrez estimer le montant de vos revenus en fonction de votre chiffre d'affaires mensuel ou annuel pour mieux gérer votre trésorerie. Ou dans le sens inverse : savoir quel montant facturer pour atteindre un certain revenu."
),
...pureSimulatorsData['eirl'].meta,
ogImage: AutoEntrepreneurPreview,
ogTitle: t(
'pages.simulateurs.eirl.meta.ogTitle',
"Dirigeant d'EIRL : calculez rapidement votre revenu net à partir du CA et vice-versa"
),
title: t(
'pages.simulateurs.eirl.meta.titre',
'EIRL : simulateur de revenus pour dirigeant'
),
},
component: IndépendantSimulation,
path: sitePaths.simulateurs.eirl,
shortName: t('pages.simulateurs.eirl.shortname', 'EIRL'),
title: t('pages.simulateurs.eirl.title', "Simulateur d'EIRL"),
nextSteps: ['comparaison-statuts'],
},
sasu: {
...pureSimulatorsData['sasu'],
config: sasuConfig,
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'SASU',
},
icône: '📘',
iframePath: 'simulateur-assimilesalarie',
meta: {
description: t(
'pages.simulateurs.sasu.meta.description',
'Calcul du salaire net à partir du total alloué à la rémunération et inversement'
),
ogDescription: t(
'pages.simulateurs.sasu.meta.ogDescription',
'En tant que dirigeant assimilé-salarié, calculez immédiatement votre revenu net après impôt à partir du total alloué à votre rémunération.'
),
...pureSimulatorsData['sasu'].meta,
ogImage: RémunérationSASUPreview,
ogTitle: t(
'pages.simulateurs.sasu.meta.ogTitle',
'Rémunération du dirigeant de SASU : un simulateur pour connaître votre salaire net'
),
title: t(
'pages.simulateurs.sasu.meta.titre',
'SASU : simulateur de revenus pour dirigeant'
),
},
path: sitePaths.simulateurs.sasu,
shortName: t('pages.simulateurs.sasu.shortname', 'SASU'),
title: t('pages.simulateurs.sasu.title', 'Simulateur de SASU'),
component: function SasuSimulation() {
return (
<>
@ -437,6 +347,7 @@ export function getSimulatorsData({
nextSteps: ['is', 'comparaison-statuts'],
},
eurl: {
...pureSimulatorsData['eurl'],
config: {
...indépendantConfig,
situation: {
@ -444,43 +355,22 @@ export function getSimulatorsData({
'entreprise . imposition': "'IS'",
},
},
tracking: {
chapter2: 'statut_entreprise',
chapter3: 'EURL',
},
icône: '📕',
iframePath: 'simulateur-eurl',
meta: {
description: t(
'pages.simulateurs.eurl.meta.description',
'Calcul du salaire net à partir du total alloué à la rémunération et inversement'
),
ogDescription: t(
'pages.simulateurs.eurl.meta.ogDescription',
'En tant que dirigeant assimilé-salarié, calculez immédiatement votre revenu net après impôt à partir du total alloué à votre rémunération.'
),
...pureSimulatorsData['eurl'].meta,
ogImage: RémunérationSASUPreview,
ogTitle: t(
'pages.simulateurs.eurl.meta.ogTitle',
"Rémunération du dirigeant d'EURL : un simulateur pour connaître votre salaire net"
),
title: t(
'pages.simulateurs.eurl.meta.titre',
'EURL : simulateur de revenus pour dirigeant'
),
},
path: sitePaths.simulateurs.eurl,
shortName: t('pages.simulateurs.sasu.shortname', 'EURL'),
title: t('pages.simulateurs.sasu.title', "Simulateur d'EURL"),
component: IndépendantSimulation,
nextSteps: ['is', 'comparaison-statuts'],
},
'auto-entrepreneur': {
...pureSimulatorsData['auto-entrepreneur'],
tracking: 'auto_entrepreneur',
config: autoEntrepreneurConfig,
icône: '🚶‍♂️',
iframePath: 'simulateur-autoentrepreneur',
meta: {
...pureSimulatorsData['auto-entrepreneur'].meta,
description: t(
'pages.simulateurs.auto-entrepreneur.meta.description',
"Calcul du revenu à partir du chiffre d'affaires, après déduction des cotisations et des impôts"
@ -577,92 +467,32 @@ export function getSimulatorsData({
nextSteps: ['indépendant', 'comparaison-statuts'],
},
indépendant: {
...pureSimulatorsData['indépendant'],
config: indépendantConfig,
tracking: 'independant',
icône: '🏃',
iframePath: 'simulateur-independant',
path: sitePaths.simulateurs.indépendant,
shortName: t('pages.simulateurs.indépendant.shortname', 'Indépendant'),
title: t(
'pages.simulateurs.indépendant.title',
'Simulateur de revenus pour indépendant'
),
meta: {
title: t(
'pages.simulateurs.indépendant.meta.title',
'Indépendant : simulateur de revenus'
),
description: t(
'pages.simulateurs.indépendant.meta.description',
"Calcul du revenu net après impôt et des cotisations à partir du chiffre d'affaires et inversement"
),
...pureSimulatorsData['indépendant'].meta,
},
component: IndépendantSimulation,
nextSteps: ['comparaison-statuts', 'is'],
},
'artiste-auteur': {
icône: '👩‍🎨',
tracking: 'artiste-auteur',
iframePath: 'simulateur-artiste-auteur',
...pureSimulatorsData['artiste-auteur'],
meta: {
title: t(
'pages.simulateurs.artiste-auteur.meta.title',
'Artiste-auteur: calcul des cotisations Urssaf'
),
description: t(
'pages.simulateurs.artiste-auteur.meta.description',
"Estimez les cotisations sociales sur les droits d'auteur et sur le revenu BNC"
),
ogTitle: 'Artiste-auteur : estimez vos cotisations Urssaf',
ogDescription:
"Renseignez vos revenus (droits d'auteur et bnc) et découvrez immédiatement le montant des cotisations que vous aurez à payer sur l'année.",
...pureSimulatorsData['artiste-auteur'].meta,
},
path: sitePaths.simulateurs['artiste-auteur'],
title: t(
'pages.simulateurs.artiste-auteur.title',
'Estimer mes cotisations dartiste-auteur'
),
shortName: t(
'pages.simulateurs.artiste-auteur.shortname',
'Artiste-auteur'
),
component: ArtisteAuteur,
},
'chômage-partiel': {
tracking: 'chomage_partiel',
...pureSimulatorsData['chômage-partiel'],
component: ChômagePartielComponent,
config: chômageParielConfig,
path: sitePaths.simulateurs['chômage-partiel'],
icône: '😷',
iframePath: 'simulateur-chomage-partiel',
meta: {
description: t(
'pages.simulateurs.chômage-partiel.meta.description',
"Calcul du revenu net pour l'employé et du reste à charge pour l'employeur après remboursement de l'Etat, en prenant en compte toutes les cotisations sociales."
),
ogDescription: t(
'pages.simulateurs.chômage-partiel.meta.ogDescription',
"Accédez à une première estimation en saisissant à partir d'un salaire brut. Vous pourrez ensuite personaliser votre situation (temps partiel, convention, etc). Prends en compte la totalité des cotisations, y compris celles spécifiques à l'indemnité (CSG et CRDS)."
),
...pureSimulatorsData['chômage-partiel'].meta,
ogImage: ChômagePartielPreview,
ogTitle: t(
'pages.simulateurs.chômage-partiel.meta.ogTitle',
"Simulateur chômage partiel : découvrez l'impact sur le revenu net salarié et le coût total employeur."
),
title: t(
'pages.simulateurs.chômage-partiel.meta.titre',
"Calcul de l'indemnité chômage partiel : le simulateur Urssaf"
),
},
shortName: t(
'pages.simulateurs.chômage-partiel.shortname',
'Chômage partiel'
),
title: t(
'pages.simulateurs.chômage-partiel.title',
'Covid-19 : Simulateur de chômage partiel'
),
seoExplanations: (
<Trans i18nKey="pages.simulateurs.chômage-partiel.seo">
<h2>Comment calculer l'indemnité d'activité partielle ?</h2>
@ -738,284 +568,137 @@ export function getSimulatorsData({
nextSteps: ['salarié', 'aides-embauche'],
},
'comparaison-statuts': {
...pureSimulatorsData['comparaison-statuts'],
component: SchemeComparaisonPage,
tracking: 'comparaison_statut',
icône: '📊',
path: sitePaths.simulateurs.comparaison,
title: t(
'pages.simulateurs.comparaison.title',
'Indépendant, assimilé salarié ou auto-entrepreneur : quel régime choisir ?'
),
meta: {
description: t(
'pages.simulateurs.comparaison.meta.description',
'Auto-entrepreneur, indépendant ou dirigeant de SASU ? Avec ce comparatif, trouvez le régime qui vous correspond le mieux'
),
title: t(
'pages.simulateurs.comparaison.meta.title',
"Création d'entreprise : le comparatif des régimes sociaux"
),
...pureSimulatorsData['comparaison-statuts'].meta,
},
shortName: t(
'pages.simulateurs.comparaison.shortname',
'Comparaison des statuts'
),
},
'économie-collaborative': {
tracking: 'economie_collaborative',
...pureSimulatorsData['économie-collaborative'],
component: ÉconomieCollaborative,
meta: {
title: t(
'pages.économie-collaborative.meta.title',
'Déclaration des revenus des plateforme en ligne : guide intéractif'
),
description: t(
'pages.économie-collaborative.meta.description',
'Airbnb, Drivy, Blablacar, Leboncoin... Découvrez comment être en règle dans vos déclarations'
),
...pureSimulatorsData['économie-collaborative'].meta,
},
icône: '🙋',
path: sitePaths.simulateurs.économieCollaborative.index,
shortName: t(
'pages.économie-collaborative.shortname',
'Guide économie collaborative'
),
},
'aide-déclaration-indépendant': {
...pureSimulatorsData['aide-déclaration-indépendant'],
component: AideDéclarationIndépendant,
tracking: {
chapter1: 'gerer',
chapter2: 'aide_declaration_independant',
},
icône: '✍️',
meta: {
description: t(
'pages.gérer.aide-déclaration-indépendant.meta.description',
'Calculer facilement les montants des charges sociales à reporter dans votre déclaration de revenu 2020.'
),
title: t(
'pages.gérer.aide-déclaration-indépendant.meta.title',
'Déclaration de revenus indépendant : calcul du montant des cotisations'
),
...pureSimulatorsData['aide-déclaration-indépendant'].meta,
},
path: sitePaths.gérer.déclarationIndépendant,
shortName: t(
'pages.gérer.aide-déclaration-indépendant.shortname',
'Aide à la déclaration de revenu'
),
title: t(
'pages.gérer.aide-déclaration-indépendant.title',
"Aide à la déclaration de revenus au titre de l'année 2020"
),
},
'demande-mobilité': {
...pureSimulatorsData['demande-mobilité'],
component: FormulaireMobilitéIndépendant,
tracking: {
chapter1: 'gerer',
chapter2: 'demande_mobilite',
},
icône: '🧳',
meta: {
title: t(
'pages.gérer.demande-mobilité.meta.title',
'Travailleur indépendant : demande de mobilité en Europe'
),
description: t(
'pages.gérer.demande-mobilité.meta.description',
"Formulaire interactif à compléter pour les indépendants souhaitant exercer leur activité dans d'autres pays d'Europe"
),
...pureSimulatorsData['demande-mobilité'].meta,
},
path: sitePaths.gérer.formulaireMobilité,
shortName: t(
'pages.gérer.demande-mobilité.shortname',
'Demande de mobilité internationale'
),
private: true,
iframePath: 'demande-mobilite',
},
médecin: {
...pureSimulatorsData['médecin'],
config: médecinConfig,
tracking: {
chapter2: 'profession_liberale',
chapter3: 'medecin',
},
icône: '⚕️',
iframePath: 'médecin',
path: sitePaths.simulateurs['profession-libérale'].médecin,
shortName: t('pages.simulateurs.médecin.shortname', 'Médecin'),
title: t(
'pages.simulateurs.médecin.title',
'Simulateur de revenus pour médecin en libéral'
),
component: IndépendantPLSimulation,
},
'chirurgien-dentiste': {
...pureSimulatorsData['chirurgien-dentiste'],
config: dentisteConfig,
icône: '🦷',
tracking: {
chapter2: 'profession_liberale',
chapter3: 'chirurgien_dentiste',
},
iframePath: 'chirurgien-dentiste',
path: sitePaths.simulateurs['profession-libérale']['chirurgien-dentiste'],
shortName: t(
'pages.simulateurs.chirurgien-dentiste.shortname',
'Chirurgien-dentiste'
),
title: t(
'pages.simulateurs.chirurgien-dentiste.title',
'Simulateur de revenus pour chirurgien-dentiste en libéral'
),
component: IndépendantPLSimulation,
},
'sage-femme': {
...pureSimulatorsData['sage-femme'],
config: sageFemmeConfig,
icône: '👶',
tracking: {
chapter2: 'profession_liberale',
chapter3: 'sage_femme',
},
iframePath: 'sage-femme',
path: sitePaths.simulateurs['profession-libérale']['sage-femme'],
shortName: t('pages.simulateurs.sage-femme.shortname', 'Sage-femme'),
title: t(
'pages.simulateurs.sage-femme.title',
'Simulateur de revenus pour sage-femme en libéral'
),
component: IndépendantPLSimulation,
},
'auxiliaire-médical': {
...pureSimulatorsData['auxiliaire-médical'],
config: auxiliaireConfig,
tracking: {
chapter2: 'profession_liberale',
chapter3: 'auxiliaire_medical',
},
tooltip: t(
'pages.simulateurs.auxiliaire.tooltip',
'Infirmiers, masseurs-kinésithérapeutes, pédicures-podologues, orthophonistes et orthoptistes'
),
icône: '🩹',
iframePath: 'auxiliaire-medical',
path: sitePaths.simulateurs['profession-libérale'].auxiliaire,
shortName: t('pages.simulateurs.auxiliaire.shortname', 'Auxiliaire méd.'),
title: t(
'pages.simulateurs.auxiliaire.title',
'Simulateur de revenus pour auxiliaire médical en libéral'
),
component: IndépendantPLSimulation,
},
avocat: {
...pureSimulatorsData['avocat'],
config: avocatConfig,
tracking: {
chapter2: 'profession_liberale',
chapter3: 'avocat',
},
icône: '⚖', // j'ai hesité avec 🥑 mais pas envie de me prendre un procès
iframePath: 'avocat',
path: sitePaths.simulateurs['profession-libérale'].avocat,
shortName: t('pages.simulateurs.avocat.shortname', 'Avocat'),
title: t(
'pages.simulateurs.avocat.title',
'Simulateur de revenus pour avocat en libéral'
),
component: IndépendantPLSimulation,
},
'expert-comptable': {
...pureSimulatorsData['expert-comptable'],
config: expertComptableConfig,
tracking: {
chapter2: 'profession_liberale',
chapter3: 'expert_comptable',
},
icône: '🧮',
iframePath: 'expert-comptable',
path: sitePaths.simulateurs['profession-libérale']['expert-comptable'],
shortName: t(
'pages.simulateurs.expert-comptable.shortname',
'Expert-Comptable'
),
title: t(
'pages.simulateurs.expert-comptable.title',
'Simulateur de revenus pour expert comptable et commissaire aux comptes en libéral'
),
component: IndépendantPLSimulation,
},
'profession-libérale': {
...pureSimulatorsData['profession-libérale'],
config: professionLibéraleConfig,
tracking: {
chapter2: 'profession_liberale',
},
icône: '💻',
meta: {
title: t(
'pages.simulateurs.profession-libérale.meta.title',
'Professions libérale : le simulateur Urssaf'
),
description: t(
'pages.simulateurs.profession-libérale.meta.description',
"Calcul du revenu net pour les indépendants en libéral à l'impôt sur le revenu (IR, BNC)"
),
...pureSimulatorsData['profession-libérale'].meta,
},
iframePath: 'profession-liberale',
path: sitePaths.simulateurs['profession-libérale'].index,
shortName: t(
'pages.simulateurs.profession-libérale.shortname',
'Profession libérale'
),
title: t(
'pages.simulateurs.profession-libérale.title',
'Simulateur de revenus pour profession libérale'
),
component: IndépendantPLSimulation,
},
pamc: {
...pureSimulatorsData['pamc'],
private: true,
iframePath: 'pamc',
tracking: {},
title: t(
'pages.simulateurs.pamc.title',
'PAMC : simulateurs de cotisations et de revenu'
),
path: sitePaths.simulateurs.pamc,
config: professionLibéraleConfig,
icône: '🏥',
meta: {
title: t(
'pages.simulateurs.pamc.meta.title',
'Simulateurs régime PAMC'
),
description: t(
'pages.simulateurs.pamc.meta.description',
'Calcul du revenu net pour les professions libérales du régime PAMC (médecin, chirurgien-dentiste, sage-femme et auxiliaire médical)'
),
...pureSimulatorsData['pamc'].meta,
},
shortName: t('pages.simulateurs.pamc.shortname', 'PAMC'),
component: PAMCHome,
},
'aides-embauche': {
icône: '🎁',
...pureSimulatorsData['aides-embauche'],
tracking: 'aides_embauche',
meta: {
title: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
description: t(
'pages.simulateurs.aides-embauche.meta.description',
'Découvrez les principales aides à lembauche et estimez leur montant en répondant à quelques questions.'
),
color: '#11965f',
...pureSimulatorsData['aides-embauche'].meta,
},
path: sitePaths.simulateurs['aides-embauche'],
iframePath: 'aides-embauche',
shortName: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
title: t(
'pages.simulateurs.aides-embauche.meta.title',
'Aides à lembauche'
),
// Cette description est surchargé car elle contient ici du JSX
description: (
<Trans i18nKey="pages.simulateurs.aides-embauche.introduction">
<p>
@ -1046,23 +729,12 @@ export function getSimulatorsData({
nextSteps: ['salarié'],
},
is: {
icône: '🗓',
...pureSimulatorsData['is'],
tracking: 'impot-societe',
path: sitePaths.simulateurs.is,
iframePath: 'impot-societe',
meta: {
title: t('pages.simulateurs.is.meta.title', 'Impôt sur les sociétés'),
description: t(
'pages.simulateurs.is.meta.description',
'Calculez votre impôt sur les sociétés'
),
color: '#E71D66',
...pureSimulatorsData['is'].meta,
},
shortName: t('pages.simulateurs.is.meta.title', 'Impôt sur les sociétés'),
title: t(
'pages.simulateurs.is.title',
"Simulateur d'impôt sur les sociétés"
),
component: ISSimulation,
seoExplanations: (
<Trans i18nKey="pages.simulateurs.is.seo">

View File

@ -136,6 +136,11 @@ module.exports.default = {
FR_BASE_URL: 'http://localhost:8080/mon-entreprise',
AT_INTERNET_SITE_ID: '',
}),
new EnvironmentPlugin({
ALGOLIA_APP_ID: '',
ALGOLIA_SEARCH_KEY: '',
ALGOLIA_INDEX_PREFIX: '',
}),
new EnvironmentPlugin({
GITHUB_REF: '',
GITHUB_HEAD_REF: '',

201
yarn.lock
View File

@ -2,6 +2,110 @@
# yarn lockfile v1
"@algolia/cache-browser-local-storage@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.2.tgz#9925c7c0ce94257564b8948b60fc427c4a98124c"
integrity sha512-B3NInwobEAim4J4Y0mgZermoi0DCXdTT/Q+4ehLamqUqxLw8To5zc9izjg7B8JaFSQsqflRdCeRmYEv2gYDY7g==
dependencies:
"@algolia/cache-common" "4.10.2"
"@algolia/cache-common@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.10.2.tgz#0113419518419895118d132bed4115345a865ce3"
integrity sha512-xcGbV0+6gLu2C7XoJdD+Pp6wWjROle6PNDsa6O21vS7fw1a03xb2bEnFdl1U31bs69P1z8IRy3h+8RVBouvhhw==
"@algolia/cache-in-memory@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.10.2.tgz#2d34d4155425b385d19ff197a8943a4b5084c790"
integrity sha512-zPIcxHQEJXy+M35A+v9Y5u5BAQOKR2aFK0kYpAdW/OrgxYcrFHtVCxwIWB/ZhGbkDtzCW8/8tJeddcD5YsHX9Q==
dependencies:
"@algolia/cache-common" "4.10.2"
"@algolia/client-account@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.10.2.tgz#c53d18d4f57ab5343c21e0ed795421964ba0cbb9"
integrity sha512-iuIU+xUtjgR9p4Hpujlr8mePDPSrVIk3peg+RAUhxniLBDaI+OhgHyhP6Lmh9flWk+JfRg91Rhk46xuxMLqwfA==
dependencies:
"@algolia/client-common" "4.10.2"
"@algolia/client-search" "4.10.2"
"@algolia/transporter" "4.10.2"
"@algolia/client-analytics@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.10.2.tgz#93c881cfb9e5df389725d821327fa801f1baa2c6"
integrity sha512-u47J65NHs0fMryDrMeuLMGjXDOKt/muF9WlfbMslT2Cvdd7PZwl9KYnT7xMhnmBB8TDiDMmEQkDykhnCOnwVNw==
dependencies:
"@algolia/client-common" "4.10.2"
"@algolia/client-search" "4.10.2"
"@algolia/requester-common" "4.10.2"
"@algolia/transporter" "4.10.2"
"@algolia/client-common@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.10.2.tgz#a715e8feb2a2b6ea38765f53e8ae6ffc4ed80aba"
integrity sha512-sfgZCv9ha9aHbe3ErAYb1blg2qx4XTLvQqP1jq8asU75rrH9XBTtSzQQO43GlArwhtwCHLgcWquN3WgPlLzkiQ==
dependencies:
"@algolia/requester-common" "4.10.2"
"@algolia/transporter" "4.10.2"
"@algolia/client-personalization@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.10.2.tgz#89d761bcf60ce13b8565c2ae8ab644c3a3d114c8"
integrity sha512-2UhUNo/czfA/keOC3+vFyMnFGV/E1Zkm+ek9Fsk/9miS39UMhx2CmH5vKSIJ7jxLSin7zBaCwKt65phfYty1pg==
dependencies:
"@algolia/client-common" "4.10.2"
"@algolia/requester-common" "4.10.2"
"@algolia/transporter" "4.10.2"
"@algolia/client-search@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.10.2.tgz#ad281b04ec4e6eaff68fb5be330f0bdf965ce011"
integrity sha512-ZdOh6XS6Y9bcekfG4y0VhdoIYfsTounsgXX4Bt3X2RCcmY3uotgaq2EVY58E6q6nvfgBfPHW18+AZCHKTWHAAw==
dependencies:
"@algolia/client-common" "4.10.2"
"@algolia/requester-common" "4.10.2"
"@algolia/transporter" "4.10.2"
"@algolia/logger-common@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.10.2.tgz#f28e966a6b878af2917ed2e1518f46650a6fb8ad"
integrity sha512-UJaU6arzmW+FT5fCv5NIbxNMtEoGcf+UENmZxxu7k7UWPARR2XL4ljJ45Jv14Z5dlz32LXWtR1PRmNfkDMk22Q==
"@algolia/logger-console@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.10.2.tgz#9d3dcbb077242db92f0f0a1795ec95c3bc839599"
integrity sha512-JrCrZ7CGs/TsyNR2AWe9Vdd6rsuxfvfcpqbu+CY7LBUYEnV8GERkf7FnDNaKVNsFJqClILCGh3U8CzQ1G5L+kA==
dependencies:
"@algolia/logger-common" "4.10.2"
"@algolia/requester-browser-xhr@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.2.tgz#2286e2f10fff3651f719b8d7d3defc8c032fcce0"
integrity sha512-LveaAp7/oCBotv1aZ4VHz8fCcJA7v/28ayh+Ljlm+hYXsxgs6NAYKz7iBpxGN7q5MV8GM+MThRYNFoT0cHTMxQ==
dependencies:
"@algolia/requester-common" "4.10.2"
"@algolia/requester-common@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.10.2.tgz#8b62f0848454ec5b07bd3599f5fb2b87ec7c4de8"
integrity sha512-3J2W0fAaURLGK0lEGeNb8eWJnQcsu+oIcfJTCIYkYT5T9w21M65kUUyD9QSf/K137qQts3tzGniUR3LxfovlXA==
"@algolia/requester-node-http@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.10.2.tgz#edb691e34e18aacc15107193319e1a712e024649"
integrity sha512-IBqsalCGgn0CrOP1PKRB5rufEOvHlrSQUFEGXZ8mxmE/zU8CLX2LKqdHbEFeNDLFl+l+8HW5BGVDGD2rvG+hSg==
dependencies:
"@algolia/requester-common" "4.10.2"
"@algolia/transporter@4.10.2":
version "4.10.2"
resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.10.2.tgz#ae0fa7c99b9bf8efa5ac83843558be1074e7c045"
integrity sha512-I3QDRSookQtPSUEnxT2XCShhipCT4beJBpWhteNwMrWQF/SqTsveqSR6bX0G49lDh9MOmYrOlCegteuKuT/tEw==
dependencies:
"@algolia/cache-common" "4.10.2"
"@algolia/logger-common" "4.10.2"
"@algolia/requester-common" "4.10.2"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@ -3476,6 +3580,23 @@
dependencies:
"@types/react" "*"
"@types/react-instantsearch-core@*":
version "6.10.2"
resolved "https://registry.yarnpkg.com/@types/react-instantsearch-core/-/react-instantsearch-core-6.10.2.tgz#634a887233ce76cc0f37a37f30909fe77a24d73b"
integrity sha512-dG/XHdrPWjVvQTTOg4Q5somVfE6xePOEFJXVeVsRNB+Pj8tzfFR6niFOStf791wGM9BKVBmmy2rCAMhcbfROnw==
dependencies:
"@types/react" "*"
algoliasearch ">=4"
algoliasearch-helper ">=3"
"@types/react-instantsearch-dom@^6.10.1":
version "6.10.1"
resolved "https://registry.yarnpkg.com/@types/react-instantsearch-dom/-/react-instantsearch-dom-6.10.1.tgz#9a0aa032c18e38c429f0d2a7c432959beb45f5a7"
integrity sha512-LISZFa3NHTB8455e+5q/igQVt11ElnUQcYp1/O+ju46Suck83EjyS4YXacUx+zTJGlagIxJadGMV+V7amJAzTQ==
dependencies:
"@types/react" "*"
"@types/react-instantsearch-core" "*"
"@types/react-native@*":
version "0.63.8"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.63.8.tgz#73ec087122c64c309eeaf150b565b8d755f0fb1f"
@ -4340,6 +4461,33 @@ ajv@^6.12.3, ajv@^6.12.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
algoliasearch-helper@>=3, algoliasearch-helper@^3.4.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.5.3.tgz#fbf8b328bc103efdefde59a7d25eaffe85b2490f"
integrity sha512-DtSlOKAJ6TGkQD6u58g6/ABdMmHf3pAj6xVL5hJF+D4z9ldDRf/f5v6puNIxGOlJRwGVvFGyz34beYNqhLDUbQ==
dependencies:
events "^1.1.1"
"algoliasearch@>= 3.27.1 < 5", algoliasearch@>=4, algoliasearch@^4.10.2:
version "4.10.2"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.10.2.tgz#23e88c71cb381d5b59430baa5d417186cc8ff683"
integrity sha512-BAYCe97XRfO15irJKBRjBnrp9tSqN0jppklLIXKdtUcXlibcPQtuAeGUP2cPiz6bJd3ISuoYzLFNt4/fQYtLMw==
dependencies:
"@algolia/cache-browser-local-storage" "4.10.2"
"@algolia/cache-common" "4.10.2"
"@algolia/cache-in-memory" "4.10.2"
"@algolia/client-account" "4.10.2"
"@algolia/client-analytics" "4.10.2"
"@algolia/client-common" "4.10.2"
"@algolia/client-personalization" "4.10.2"
"@algolia/client-search" "4.10.2"
"@algolia/logger-common" "4.10.2"
"@algolia/logger-console" "4.10.2"
"@algolia/requester-browser-xhr" "4.10.2"
"@algolia/requester-common" "4.10.2"
"@algolia/requester-node-http" "4.10.2"
"@algolia/transporter" "4.10.2"
ally.js@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/ally.js/-/ally.js-1.4.1.tgz#9fb7e6ba58efac4ee9131cb29aa9ee3b540bcf1e"
@ -7809,6 +7957,11 @@ eventemitter3@^4.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.5.tgz#51d81e4f1ccc8311a04f0c20121ea824377ea6d9"
integrity sha512-QR0rh0YiPuxuDQ6+T9GAO/xWTExXpxIes1Nl9RykNGTnE1HJmkuEfxJH9cubjIOQZ/GH4qNBR4u8VSHaKiWs4g==
events@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
events@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
@ -9518,6 +9671,11 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"
instantsearch.css@^7.4.5:
version "7.4.5"
resolved "https://registry.yarnpkg.com/instantsearch.css/-/instantsearch.css-7.4.5.tgz#2a521aa634329bf1680f79adf87c79d67669ec8d"
integrity sha512-iIGBYjCokU93DDB8kbeztKtlu4qVEyTg1xvS6iSO1YvqRwkIZgf0tmsl/GytsLdZhuw8j4wEaeYsCzNbeJ/zEQ==
internal-slot@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
@ -13345,7 +13503,7 @@ react-easy-emoji@^1.2.0, react-easy-emoji@^1.4.0:
lodash.assign "^4.0.8"
string-replace-to-array "^1.0.1"
react-fast-compare@^3.1.1:
react-fast-compare@^3.0.0, react-fast-compare@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
@ -13382,6 +13540,47 @@ react-i18next@^11.0.0:
"@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1"
react-instantsearch-core@^6.11.2:
version "6.11.2"
resolved "https://registry.yarnpkg.com/react-instantsearch-core/-/react-instantsearch-core-6.11.2.tgz#5d70b04b02a91f2729e664156e6cd5203fae2c26"
integrity sha512-DSvS8XRESmhuBp9q+lhhsGqEKupWJioe95CCelUH0RoB8RtdC2vXRvBMDBEqTf7vG5K7b/dbbObBj5PnMqv5Sw==
dependencies:
"@babel/runtime" "^7.1.2"
algoliasearch-helper "^3.4.3"
prop-types "^15.6.2"
react-fast-compare "^3.0.0"
react-instantsearch-dom@^6.11.2:
version "6.11.2"
resolved "https://registry.yarnpkg.com/react-instantsearch-dom/-/react-instantsearch-dom-6.11.2.tgz#048e8934dfac472eb59a16fa0125fda0669334c6"
integrity sha512-n6d0E9rreGHIo88OuWHagabAwS9/6NQ/vxRivi0n1Zfqhkaota6QTh83Ay2HIBnio1kRuJgqJhLpO+85R4vzvQ==
dependencies:
"@babel/runtime" "^7.1.2"
algoliasearch-helper "^3.4.3"
classnames "^2.2.5"
prop-types "^15.6.2"
react-fast-compare "^3.0.0"
react-instantsearch-core "^6.11.2"
react-instantsearch-native@^6.11.2:
version "6.11.2"
resolved "https://registry.yarnpkg.com/react-instantsearch-native/-/react-instantsearch-native-6.11.2.tgz#4493e05f7efbd1cb6bd394eac568075d86bb200c"
integrity sha512-T4fXONNRXx5HkUVN2qIcZifIivuIG7vMNDsgGtKL3OM5+eUvu+jZYRdBOX0c2WY3S036jTRoFiweVgxYI1xIig==
dependencies:
"@babel/runtime" "^7.1.2"
algoliasearch ">= 3.27.1 < 5"
react-instantsearch-core "^6.11.2"
react-instantsearch@^6.11.2:
version "6.11.2"
resolved "https://registry.yarnpkg.com/react-instantsearch/-/react-instantsearch-6.11.2.tgz#5df882a9bbfd65552c50ebc3f2874df21662e1b6"
integrity sha512-SlB4MdT4kJx9upGh6enf0XJJJ6byGLt/09rCbdJDRTapTqfn85PCZ7LXBFapLU0BEnJHtGOWfdZMHnRQ0FET9w==
dependencies:
"@babel/runtime" "^7.1.2"
react-instantsearch-core "^6.11.2"
react-instantsearch-dom "^6.11.2"
react-instantsearch-native "^6.11.2"
"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"