From 50826d74e48f2b7fa05854a074b021d9ada5384c Mon Sep 17 00:00:00 2001 From: Alexandre S Date: Mon, 28 Jun 2021 19:30:15 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Ajoute=20une=20recherche=20globa?= =?UTF-8?q?le=20de=20simulateurs=20et=20de=20r=C3=A8gles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Maxime Quandalle Co-authored-by: Alexandre Hajjar --- .github/workflows/deploy.yaml | 10 + CONTRIBUTING.md | 5 +- mon-entreprise/.env.template | 11 + .../integration/mon-entreprise/recherche.js | 29 + mon-entreprise/package.json | 8 +- mon-entreprise/scripts/search/update-data.js | 157 +++++ mon-entreprise/source/App.css | 15 + .../source/components/RulesList.tsx | 14 - .../source/components/SearchBar.css | 8 - .../source/components/SearchBar.tsx | 184 ------ .../source/components/SearchBar.worker.js | 32 - .../source/components/SearchButton.tsx | 82 ++- .../source/components/Simulation.tsx | 2 - .../source/components/layout/Header.tsx | 2 + .../components/search/RulesInfiniteHits.tsx | 70 +++ .../source/components/search/SearchRules.tsx | 36 ++ .../search/SearchRulesAndSimulators.css | 57 ++ .../search/SearchRulesAndSimulators.tsx | 47 ++ .../components/search/SimulatorHits.tsx | 57 ++ .../source/components/ui/breakpoints.ts | 7 + mon-entreprise/source/locales/ui-en.yaml | 3 + mon-entreprise/source/locales/ui-fr.yaml | 3 + mon-entreprise/source/pages/Documentation.tsx | 28 +- .../source/pages/Simulateurs/metadata-src.js | 567 ++++++++++++++++++ .../source/pages/Simulateurs/metadata.tsx | 416 ++----------- mon-entreprise/webpack.common.js | 5 + yarn.lock | 201 ++++++- 27 files changed, 1416 insertions(+), 640 deletions(-) create mode 100644 mon-entreprise/.env.template create mode 100644 mon-entreprise/cypress/integration/mon-entreprise/recherche.js create mode 100644 mon-entreprise/scripts/search/update-data.js delete mode 100644 mon-entreprise/source/components/RulesList.tsx delete mode 100644 mon-entreprise/source/components/SearchBar.css delete mode 100644 mon-entreprise/source/components/SearchBar.tsx delete mode 100644 mon-entreprise/source/components/SearchBar.worker.js create mode 100644 mon-entreprise/source/components/search/RulesInfiniteHits.tsx create mode 100644 mon-entreprise/source/components/search/SearchRules.tsx create mode 100644 mon-entreprise/source/components/search/SearchRulesAndSimulators.css create mode 100644 mon-entreprise/source/components/search/SearchRulesAndSimulators.tsx create mode 100644 mon-entreprise/source/components/search/SimulatorHits.tsx create mode 100644 mon-entreprise/source/components/ui/breakpoints.ts create mode 100644 mon-entreprise/source/pages/Simulateurs/metadata-src.js diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index ed0400484..89c2a12ed 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95692e251..1d2fd639d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/mon-entreprise/.env.template b/mon-entreprise/.env.template new file mode 100644 index 000000000..0958048ff --- /dev/null +++ b/mon-entreprise/.env.template @@ -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= diff --git a/mon-entreprise/cypress/integration/mon-entreprise/recherche.js b/mon-entreprise/cypress/integration/mon-entreprise/recherche.js new file mode 100644 index 000000000..55734fcf6 --- /dev/null +++ b/mon-entreprise/cypress/integration/mon-entreprise/recherche.js @@ -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) + }) +}) diff --git a/mon-entreprise/package.json b/mon-entreprise/package.json index 1fbc42a06..4860cdbb5 100644 --- a/mon-entreprise/package.json +++ b/mon-entreprise/package.json @@ -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" } } diff --git a/mon-entreprise/scripts/search/update-data.js b/mon-entreprise/scripts/search/update-data.js new file mode 100644 index 000000000..01cb12990 --- /dev/null +++ b/mon-entreprise/scripts/search/update-data.js @@ -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: '', + highlightPostTag: '', + 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: '', + highlightPostTag: '', + 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)) + } +})() diff --git a/mon-entreprise/source/App.css b/mon-entreprise/source/App.css index d2b1ef450..9427edd10 100644 --- a/mon-entreprise/source/App.css +++ b/mon-entreprise/source/App.css @@ -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; +} diff --git a/mon-entreprise/source/components/RulesList.tsx b/mon-entreprise/source/components/RulesList.tsx deleted file mode 100644 index 7be5822f2..000000000 --- a/mon-entreprise/source/components/RulesList.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import SearchBar from 'Components/SearchBar' -import { Trans } from 'react-i18next' -import './RulesList.css' - -export default function RulesList() { - return ( -
-

- Explorez notre documentation -

- -
- ) -} diff --git a/mon-entreprise/source/components/SearchBar.css b/mon-entreprise/source/components/SearchBar.css deleted file mode 100644 index 0dc42725b..000000000 --- a/mon-entreprise/source/components/SearchBar.css +++ /dev/null @@ -1,8 +0,0 @@ -li.active { - background: var(--color); - color: var(--textColor); -} - -li.active a { - color: var(--textColor); -} diff --git a/mon-entreprise/source/components/SearchBar.tsx b/mon-entreprise/source/components/SearchBar.tsx deleted file mode 100644 index 1d48879eb..000000000 --- a/mon-entreprise/source/components/SearchBar.tsx +++ /dev/null @@ -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 -} - -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, - - {currentStr} - , - ], - ] as [boolean, number, Array] - }, - [false, 0, []] as [boolean, number, Array] - )[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 = 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 ( - <> - { - const input = e.target.value - if (input.length > 0) worker.postMessage({ input }) - setInput(input) - }} - /> - {!!input.length && !results.length ? ( -

- - Aucun résultat ne correspond à cette recherche - -

- ) : ( -
    - {(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 }) => ( -
  • - - - {item.espace - .slice(1) - .reverse() - .map((name) => ( - - {highlightMatches( - name, - matches.filter( - (m) => m.key === 'espace' && m.value === name - ) - )}{' '} - ›{' '} - - ))} -
    -
    - {highlightMatches( - item.title, - matches.filter((m) => m.key === 'title') - )} -
    -
  • - ))} -
- )} - - ) -} diff --git a/mon-entreprise/source/components/SearchBar.worker.js b/mon-entreprise/source/components/SearchBar.worker.js deleted file mode 100644 index 70adc434e..000000000 --- a/mon-entreprise/source/components/SearchBar.worker.js +++ /dev/null @@ -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) - } -} diff --git a/mon-entreprise/source/components/SearchButton.tsx b/mon-entreprise/source/components/SearchButton.tsx index 0a55202ae..61a08aab5 100644 --- a/mon-entreprise/source/components/SearchButton.tsx +++ b/mon-entreprise/source/components/SearchButton.tsx @@ -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 ? ( - -

- Chercher dans la documentation -

- -
- ) : invisibleButton ? null : ( - + useEffect(() => { + if (pathname !== pathnameRef.current) { + pathnameRef.current = pathname + close() + } + }, [pathname]) + + return ( + <> + {visible && ( + +

+ Que cherchez-vous ? +

+ +
+ )} + + setVisible(true)} + id="search-display-button" + > + + + +
+ Rechercher +
+
+ ) } diff --git a/mon-entreprise/source/components/Simulation.tsx b/mon-entreprise/source/components/Simulation.tsx index fc1f7e180..6141ceccc 100644 --- a/mon-entreprise/source/components/Simulation.tsx +++ b/mon-entreprise/source/components/Simulation.tsx @@ -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({ {simulationBloc} - {!firstStepCompleted && } {firstStepCompleted && ( <> diff --git a/mon-entreprise/source/components/layout/Header.tsx b/mon-entreprise/source/components/layout/Header.tsx index d0add527c..c94883ccd 100644 --- a/mon-entreprise/source/components/layout/Header.tsx +++ b/mon-entreprise/source/components/layout/Header.tsx @@ -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() { > logo urssaf + {language === 'fr' && } diff --git a/mon-entreprise/source/components/search/RulesInfiniteHits.tsx b/mon-entreprise/source/components/search/RulesInfiniteHits.tsx new file mode 100644 index 000000000..aac7bb158 --- /dev/null +++ b/mon-entreprise/source/components/search/RulesInfiniteHits.tsx @@ -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 ( + + {hit.namespace && ( +
+ +
+ )} +
+ +
+
+ ) +} + +const HideableTitle = connectStats(({ nbHits }) => { + return nbHits === 0 ? ( + <> + ) : ( +

+ Règles de calculs +

+ ) +}) + +const Hits = connectInfiniteHits(({ hits, hasMore, refineNext }) => { + const { t } = useTranslation() + return ( +
+
+ <> +
    + {hits.map((hit) => ( +
  1. + +
  2. + ))} +
+ {hasMore && ( + + )} + +
+
+ ) +}) + +export const RulesInfiniteHits = () => { + return ( + <> + + + + ) +} diff --git a/mon-entreprise/source/components/search/SearchRules.tsx b/mon-entreprise/source/components/search/SearchRules.tsx new file mode 100644 index 000000000..278d1b25a --- /dev/null +++ b/mon-entreprise/source/components/search/SearchRules.tsx @@ -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 ( + + +

+ Règles de calculs +

+ +
+ ) +} diff --git a/mon-entreprise/source/components/search/SearchRulesAndSimulators.css b/mon-entreprise/source/components/search/SearchRulesAndSimulators.css new file mode 100644 index 000000000..c52461caa --- /dev/null +++ b/mon-entreprise/source/components/search/SearchRulesAndSimulators.css @@ -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; +} diff --git a/mon-entreprise/source/components/search/SearchRulesAndSimulators.tsx b/mon-entreprise/source/components/search/SearchRulesAndSimulators.tsx new file mode 100644 index 000000000..0d1a228d8 --- /dev/null +++ b/mon-entreprise/source/components/search/SearchRulesAndSimulators.tsx @@ -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 ( + + + + + + + + + + + + + ) +} diff --git a/mon-entreprise/source/components/search/SimulatorHits.tsx b/mon-entreprise/source/components/search/SimulatorHits.tsx new file mode 100644 index 000000000..08bb076f8 --- /dev/null +++ b/mon-entreprise/source/components/search/SimulatorHits.tsx @@ -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) => ( + +
+ {hit.icône && }{' '} +
+ + +) + +type SimulatorHitsProps = { + hits: Array +} + +export const SimulatorHits = connectHits(({ hits }: SimulatorHitsProps) => { + const sitePaths = useContext(SitePathsContext) + return ( + <> + {hits.length > 0 && ( +

+ Simulateurs +

+ )} +
+ {hits.map((hit) => ( + + ))} +
+ + ) +}) diff --git a/mon-entreprise/source/components/ui/breakpoints.ts b/mon-entreprise/source/components/ui/breakpoints.ts new file mode 100644 index 000000000..17175b5bc --- /dev/null +++ b/mon-entreprise/source/components/ui/breakpoints.ts @@ -0,0 +1,7 @@ +type Screen = 'phone' | 'tablet' | 'desktop' + +export const breakpoints: Record = { + phone: '0px', + tablet: '850px', + desktop: '1200px', +} diff --git a/mon-entreprise/source/locales/ui-en.yaml b/mon-entreprise/source/locales/ui-en.yaml index c493bb686..5ccd196e0 100644 --- a/mon-entreprise/source/locales/ui-en.yaml +++ b/mon-entreprise/source/locales/ui-en.yaml @@ -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 diff --git a/mon-entreprise/source/locales/ui-fr.yaml b/mon-entreprise/source/locales/ui-fr.yaml index e46b1fd2a..97ce35777 100644 --- a/mon-entreprise/source/locales/ui-fr.yaml +++ b/mon-entreprise/source/locales/ui-fr.yaml @@ -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 diff --git a/mon-entreprise/source/pages/Documentation.tsx b/mon-entreprise/source/pages/Documentation.tsx index 44117b18c..3b4e844e4 100644 --- a/mon-entreprise/source/pages/Documentation.tsx +++ b/mon-entreprise/source/pages/Documentation.tsx @@ -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 } + + if (pathname === '/documentation/dev') { + return + } + if (!documentationSitePaths[pathname]) { return } + return ( {currentSimulation ? : } - Documentation

Explorez toutes les règles de la documentation

- + + + ) +} + +function DocumentationRulesList() { + const ruleEntries = Object.keys(rules) as DottedName[] + return ( + <> +

Liste des règles

+ {ruleEntries.map((name) => ( + + {name} + + ))} ) } diff --git a/mon-entreprise/source/pages/Simulateurs/metadata-src.js b/mon-entreprise/source/pages/Simulateurs/metadata-src.js new file mode 100644 index 000000000..88df8c6c4 --- /dev/null +++ b/mon-entreprise/source/pages/Simulateurs/metadata-src.js @@ -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 d’artiste-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 à l’embauche' + ), + description: t( + 'pages.simulateurs.aides-embauche.meta.description', + 'Découvrez les principales aides à l’embauche 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 à l’embauche' + ), + title: t( + 'pages.simulateurs.aides-embauche.meta.title', + 'Aides à l’embauche' + ), + 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'], + }, + } +} diff --git a/mon-entreprise/source/pages/Simulateurs/metadata.tsx b/mon-entreprise/source/pages/Simulateurs/metadata.tsx index 6e49bb703..d6a67be2e 100644 --- a/mon-entreprise/source/pages/Simulateurs/metadata.tsx +++ b/mon-entreprise/source/pages/Simulateurs/metadata.tsx @@ -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: (

Comment calculer le salaire net ?

@@ -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: (

@@ -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 d’artiste-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: (

Comment calculer l'indemnité d'activité partielle ?

@@ -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 à l’embauche' - ), - description: t( - 'pages.simulateurs.aides-embauche.meta.description', - 'Découvrez les principales aides à l’embauche 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 à l’embauche' - ), - title: t( - 'pages.simulateurs.aides-embauche.meta.title', - 'Aides à l’embauche' - ), + // Cette description est surchargé car elle contient ici du JSX description: (

@@ -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: ( diff --git a/mon-entreprise/webpack.common.js b/mon-entreprise/webpack.common.js index 0117c2e6f..648fb98d3 100644 --- a/mon-entreprise/webpack.common.js +++ b/mon-entreprise/webpack.common.js @@ -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: '', diff --git a/yarn.lock b/yarn.lock index c612fa625..6a4148743 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"