Update react-router to v6

pull/2249/head
Jérémy Rialland 2022-07-25 11:02:08 +02:00 committed by Jérémy Rialland
parent cb683f0263
commit 5bca6eeb02
51 changed files with 471 additions and 569 deletions

View File

@ -88,9 +88,7 @@
"react-instantsearch": "^6.11.2",
"react-instantsearch-dom": "^6.11.2",
"react-redux": "^7.2.8",
"react-router": "^5.3.3",
"react-router-dom": "^5.3.3",
"react-router-dom-v5-compat": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-signature-pad-wrapper": "^1.2.11",
"react-spring": "^9.3.1",
"react-use-measure": "^2.0.4",
@ -121,8 +119,6 @@
"@types/react-dom": "^17.0.9",
"@types/react-instantsearch-dom": "^6.10.1",
"@types/react-redux": "^7.1.23",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.3.3",
"@types/recharts": "^1.8.16",
"@types/serve-static": "^1.13.10",
"@types/styled-components": "^5.1.24",

View File

@ -27,12 +27,12 @@ export const pagesToPrerender: {
sitePathFr.simulateurs['artiste-auteur'],
'/iframes/simulateur-embauche',
'/iframes/pamc',
],
].map((url) => decodeURI(url)),
infrance: [
sitePathEn.index,
sitePathEn.simulateurs.salarié,
'/iframes/simulateur-embauche',
],
].map((url) => decodeURI(url)),
}
const dev = argv.findIndex((val) => val === '--dev') > -1

View File

@ -22,8 +22,7 @@ import { ComponentProps, StrictMode, useContext, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { Redirect, Route, Switch } from 'react-router-dom'
import { CompatRoute } from 'react-router-dom-v5-compat'
import { Route, Routes } from 'react-router-dom'
import styled, { css } from 'styled-components'
import Accessibilité from './pages/Accessibilité'
import Budget from './pages/Budget/Budget'
@ -91,20 +90,11 @@ const Router = () => {
return (
<SituationProvider situation={situation}>
<Switch>
<Route exact path="/" component={Landing} />
{/* Removes trailing slashes */}
<Route
path={'/:url*(/+)'}
exact
strict
render={({ location }) => (
<Redirect to={location.pathname.replace(/\/+$/, location.search)} />
)}
/>
<Route path="/iframes" component={Iframes} />
<Route component={App} />
</Switch>
<Routes>
<Route index element={<Landing />} />
<Route path="/iframes/*" element={<Iframes />} />
<Route path="*" element={<App />} />
</Routes>
</SituationProvider>
)
}
@ -131,30 +121,34 @@ const App = () => {
<Container>
<ErrorBoundary fallback={CatchOffline}>
{/* Passing location down to prevent update blocking */}
<Switch>
<Route path={sitePaths.créer.index} component={Créer} />
<Route path={sitePaths.gérer.index} component={Gérer} />
<Route path={sitePaths.simulateurs.index} component={Simulateurs} />
<Routes>
<Route path={sitePaths.créer.index + '/*'} element={<Créer />} />
<Route path={sitePaths.gérer.index + '/*'} element={<Gérer />} />
<Route
path={sitePaths.documentation.index}
component={Documentation}
path={sitePaths.simulateurs.index + '/*'}
element={<Simulateurs />}
/>
<Route path={sitePaths.développeur.index} component={Integration} />
<Route path={sitePaths.nouveautés} component={Nouveautés} />
<CompatRoute path={sitePaths.stats} component={Stats} />
<Route path={sitePaths.budget} component={Budget} />
<Route path={sitePaths.accessibilité} component={Accessibilité} />
<Route
exact
path="/dev/integration-test"
component={IntegrationTest}
path={sitePaths.documentation.index + '/*'}
element={<Documentation />}
/>
<Route exact path="/dev/personas" component={Personas} />
<Route
path={sitePaths.développeur.index + '/*'}
element={<Integration />}
/>
<Route
path={sitePaths.nouveautés + '/*'}
element={<Nouveautés />}
/>
<Route path={sitePaths.stats} element={<Stats />} />
<Route path={sitePaths.budget} element={<Budget />} />
<Route path={sitePaths.accessibilité} element={<Accessibilité />} />
<Route component={Route404} />
</Switch>
<Route path="/dev/integration-test" element={<IntegrationTest />} />
<Route path="/dev/personas" element={<Personas />} />
<Route path="*" element={<Route404 />} />
</Routes>
<Spacing xxl />
</ErrorBoundary>
</Container>

View File

@ -21,7 +21,6 @@ import { HelmetProvider } from 'react-helmet-async'
import { I18nextProvider } from 'react-i18next'
import { Provider as ReduxProvider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { ServiceWorker } from './ServiceWorker'
import * as safeLocalStorage from './storage/safeLocalStorage'
import { store } from './store'
@ -94,7 +93,7 @@ export default function Provider({
<SitePathProvider value={sitePaths}>
<I18nextProvider i18n={i18next}>
<BrowserRouterProvider basename={basename}>
<>{children}</>
{children}
</BrowserRouterProvider>
</I18nextProvider>
</SitePathProvider>
@ -116,7 +115,7 @@ function BrowserRouterProvider({
children: ReactNode
basename: string
}) {
// The server rouer is only provided in the entry-server file
// The server router is only provided in the entry-server file
if (import.meta.env.SSR) {
return <>{children}</>
}
@ -139,7 +138,7 @@ function BrowserRouterProvider({
<BrowserRouter
basename={import.meta.env.MODE === 'production' ? '' : basename}
>
<CompatRouter>{children}</CompatRouter>
{children}
</BrowserRouter>
</TrackingContext.Provider>
</HelmetProvider>

View File

@ -2,7 +2,7 @@ import { useNextQuestionUrl } from '@/selectors/companyStatusSelectors'
import { LegalStatusRequirements } from '@/types/companyTypes'
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom-v5-compat'
import { useNavigate } from 'react-router-dom'
import { Action } from './actions'
export type CompanyStatusAction = ReturnType<

View File

@ -1,7 +1,7 @@
import { ScrollToElement } from '@/components/utils/Scroll'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components'
declare global {

View File

@ -8,7 +8,7 @@ import { Body, SmallBody } from '@/design-system/typography/paragraphs'
import { CurrentSimulatorDataContext } from '@/pages/Simulateurs/metadata'
import React, { useCallback, useContext, useState } from 'react'
import { Trans } from 'react-i18next'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import styled from 'styled-components'
import { TrackingContext } from '../../ATInternetTracking'
import * as safeLocalStorage from '../../storage/safeLocalStorage'

View File

@ -1,9 +1,9 @@
import { Grid } from '@/design-system/layout'
import { SmallCard } from '@/design-system/card'
import { Grid } from '@/design-system/layout'
import { H2 } from '@/design-system/typography/heading'
import { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
import { icons } from './ui/SocialIcon'
import Emoji from './utils/Emoji'
import { SitePathsContext } from './utils/SitePathsContext'
@ -17,6 +17,8 @@ export default function MoreInfosOnUs() {
return null
}
console.log(pathname, sitePaths.nouveautés)
return (
<>
<H2>Plus d'infos sur mon-entreprise</H2>

View File

@ -1,6 +1,6 @@
import image from '@/images/map-directions.png'
import { Trans } from 'react-i18next'
import { Link } from 'react-router-dom-v5-compat'
import { Link } from 'react-router-dom'
import Emoji from './utils/Emoji'
export default function Route404() {

View File

@ -29,7 +29,7 @@ export default function Footer() {
(currentEnv === 'production' || currentEnv === 'development'
? `${window.location.protocol}//${window.location.host}`
: '') + window.location.pathname
const uri = decodeURIComponent(encodedUri || '').replace(/\/$/, '')
const uri = (encodedUri || '').replace(/\/$/, '')
const hrefLink = hrefLangLink[language][uri]
return (

View File

@ -9,7 +9,7 @@ export const useShowFeedback = () => {
const simulators = useSimulatorsData()
const blacklisted = [
sitePath.gérer.déclarationIndépendant.cotisations as string,
sitePath.gérer.déclarationIndépendant.beta.cotisations as string,
].includes(currentPath)
if (blacklisted) {

View File

@ -1,12 +1,12 @@
import algoliasearch from 'algoliasearch/lite'
import { Spacing } from '@/design-system/layout'
import algoliasearch from 'algoliasearch/lite'
import { useEffect, useRef } from 'react'
import { Configure, Index } from 'react-instantsearch-dom'
import { useLocation } from 'react-router-dom'
import { RulesInfiniteHits } from './RulesInfiniteHits'
import { SearchBox } from './SearchBox'
import { SearchRoot } from './SearchRoot'
import { SimulatorHits } from './SimulatorHits'
import { useLocation } from 'react-router-dom-v5-compat'
const ALGOLIA_APP_ID = import.meta.env.VITE_ALGOLIA_APP_ID || ''
const ALGOLIA_SEARCH_KEY = import.meta.env.VITE_ALGOLIA_SEARCH_KEY || ''

View File

@ -1,8 +1,8 @@
import { Grid } from '@/design-system/layout'
import Emoji from '@/components/utils/Emoji'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { SmallCard } from '@/design-system/card'
import InfoBulle from '@/design-system/InfoBulle'
import { Grid } from '@/design-system/layout'
import { H3 } from '@/design-system/typography/heading'
import { ExtractFromSimuData } from '@/pages/Simulateurs/metadata'
import { MetadataSrc } from '@/pages/Simulateurs/metadata-src'
@ -34,10 +34,8 @@ const SimulateurCardHit = ({
return (
<SmallCard
icon={<Emoji emoji={hit.icône} />}
to={{
state: { fromSimulateurs: true },
pathname: path,
}}
to={{ pathname: path }}
state={{ fromSimulateurs: true }}
title={
<h4>
<Highlight hit={hit} attribute="title" />{' '}

View File

@ -1,6 +1,6 @@
import { Helmet } from 'react-helmet-async'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom-v5-compat'
import { useLocation } from 'react-router-dom'
type PropType = {
page: string

View File

@ -1,4 +1,3 @@
import { TrackingContext } from '@/ATInternetTracking'
import React, {
createContext,
ReactNode,
@ -47,7 +46,10 @@ export function useIsEmbedded() {
export function IsEmbeded({ children }: { children: React.ReactNode }) {
const setEmbedded = useContext(IsEmbeddedContext)[1]
setEmbedded(true)
useEffect(() => {
setEmbedded(true)
}, [setEmbedded])
return <IsEmbeddedProvider isEmbeded>{children}</IsEmbeddedProvider>
}

View File

@ -1,3 +1,4 @@
import { Message } from '@/design-system'
import { Strong, U } from '@/design-system/typography'
import { H1, H2, H3, H4, H5, H6 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
@ -5,11 +6,10 @@ import { Li, Ol, Ul } from '@/design-system/typography/list'
import { Body } from '@/design-system/typography/paragraphs'
import MarkdownToJsx, { MarkdownToJSX } from 'markdown-to-jsx'
import React, { useContext, useEffect } from 'react'
import { useLocation } from 'react-router-dom-v5-compat'
import { isIterable } from '../../utils'
import { useLocation } from 'react-router-dom'
import { SiteNameContext } from '../../Provider'
import { isIterable } from '../../utils'
import Emoji from './Emoji'
import { Message } from '@/design-system'
const internalURLs = {
'mon-entreprise.urssaf.fr': 'mon-entreprise',

View File

@ -1,12 +1,12 @@
import { useEffect, useMemo, useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { RootState, SimulationConfig, Situation } from '@/reducers/rootReducer'
import { useLocation, useSearchParams } from 'react-router-dom-v5-compat'
import { batchUpdateSituation, setActiveTarget } from '@/actions/actions'
import { useEngine } from '@/components/utils/EngineContext'
import { RootState, SimulationConfig, Situation } from '@/reducers/rootReducer'
import { configSelector } from '@/selectors/simulationSelectors'
import Engine, { ParsedRules, serializeEvaluation } from 'publicodes'
import { DottedName } from 'modele-social'
import { setActiveTarget, batchUpdateSituation } from '@/actions/actions'
import Engine, { ParsedRules, serializeEvaluation } from 'publicodes'
import { useEffect, useMemo, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useLocation, useSearchParams } from 'react-router-dom'
type Objectifs = (string | { objectifs: string[] })[]
type ShortName = string

View File

@ -14,12 +14,16 @@ export default function useSimulationConfig(
const dispatch = useDispatch()
const lastConfig = useSelector(configSelector)
if (config && lastConfig !== config) {
dispatch(setSimulationConfig(config ?? {}, path))
}
useEffect(() => {
if (config && lastConfig !== config) {
dispatch(setSimulationConfig(config ?? {}, path))
}
}, [config, dispatch, lastConfig, path])
useEffect(() => {
if (autoloadLastSimulation) {
dispatch(loadPreviousSimulation())
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}

View File

@ -10,7 +10,7 @@ import {
import { Body } from '@/design-system/typography/paragraphs'
import { useButton } from '@react-aria/button'
import React, { useRef } from 'react'
import { Link } from 'react-router-dom-v5-compat'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { GenericCardProps, getTitleProps } from './Card'

View File

@ -1,4 +1,3 @@
import { useButton } from '@react-aria/button'
import { FocusStyle } from '@/design-system/global-style'
import { H6 } from '@/design-system/typography/heading'
import {
@ -6,8 +5,9 @@ import {
useExternalLinkProps,
} from '@/design-system/typography/link'
import { SmallBody } from '@/design-system/typography/paragraphs'
import { useButton } from '@react-aria/button'
import React, { useRef } from 'react'
import { Link } from 'react-router-dom-v5-compat'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { GenericCardProps, getTitleProps } from './Card'

View File

@ -1,7 +1,7 @@
import { Button } from '@/design-system/buttons'
import { useOverlayTrigger } from '@react-aria/overlays'
import { useOverlayTriggerState } from '@react-stately/overlays'
import { AriaButtonProps } from '@react-types/button'
import { Button } from '@/design-system/buttons'
import React, {
ReactElement,
Ref,
@ -10,9 +10,9 @@ import React, {
useMemo,
useRef,
} from 'react'
import { useLocation } from 'react-router-dom-v5-compat'
import Popover from './Popover'
import { useLocation } from 'react-router-dom'
import { Link } from '../typography/link'
import Popover from './Popover'
type ButtonBuilderProps = AriaButtonProps & {
ref: Ref<HTMLButtonElement>

View File

@ -1,14 +1,11 @@
import { useProgressBar } from '@react-aria/progress'
import { useSSRSafeId } from '@react-aria/ssr'
import { Trans } from 'react-i18next'
import { AriaButtonProps } from '@react-types/button'
import { ComponentPropsWithRef } from 'react'
import { Trans } from 'react-i18next'
import { Link as RouterLink, useMatch } from 'react-router-dom'
import styled, { css } from 'styled-components'
import { Link } from '../typography/link'
import { Link as RouterLink } from 'react-router-dom-v5-compat'
import { useRouteMatch } from 'react-router'
import { ComponentPropsWithRef } from 'react'
type Props = {
isDisabled?: boolean
@ -27,7 +24,7 @@ export function Step({
if (import.meta.env.DEV && (progress > 1 || progress < 0)) {
throw new TypeError('`progress` should be a number between 0 and 1')
}
const active = !!useRouteMatch({ path: props.to, exact: true })
const active = !!useMatch({ path: props.to })
const propsBar = {
'aria-labelledby': labelId,
minValue: 0,

View File

@ -1,8 +1,7 @@
import { SSRProvider } from '@react-aria/ssr'
import ReactDOMServer from 'react-dom/server'
import { FilledContext, HelmetProvider } from 'react-helmet-async'
import { StaticRouter } from 'react-router-dom'
import { CompatRouter } from 'react-router-dom-v5-compat'
import { StaticRouter } from 'react-router-dom/server'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
import { AppEn } from './entry-en'
import { AppFr } from './entry-fr'
@ -22,9 +21,7 @@ export function render(url: string, lang: 'fr' | 'en') {
<SSRProvider>
<StyleSheetManager sheet={sheet.instance}>
<StaticRouter location={url}>
<CompatRouter>
<App />
</CompatRouter>
<App />
</StaticRouter>
</StyleSheetManager>
</SSRProvider>

View File

@ -454,10 +454,8 @@ export default function CreateCompany({ statut }: CreateCompanyProps) {
)}
</h3>
}
to={{
pathname: sitePaths.simulateurs['auto-entrepreneur'],
state: { fromCréer: true },
}}
to={{ pathname: sitePaths.simulateurs['auto-entrepreneur'] }}
state={{ fromCréer: true }}
ctaLabel={t(
'entreprise.ressources.simu.autoEntrepreneur.cta',
'Simuler les revenus'
@ -481,10 +479,8 @@ export default function CreateCompany({ statut }: CreateCompanyProps) {
)}
</h3>
}
to={{
pathname: sitePaths.simulateurs.indépendant,
state: { fromCréer: true },
}}
to={{ pathname: sitePaths.simulateurs.indépendant }}
state={{ fromCréer: true }}
ctaLabel={t(
'entreprise.ressources.simu.indépendant.cta',
'Simuler les cotisations'
@ -508,10 +504,8 @@ export default function CreateCompany({ statut }: CreateCompanyProps) {
)}
</h3>
}
to={{
pathname: sitePaths.simulateurs.sasu,
state: { fromCréer: true },
}}
to={{ pathname: sitePaths.simulateurs.sasu }}
state={{ fromCréer: true }}
ctaLabel={t(
'entreprise.ressources.simu.assimilé.cta',
'Simuler la rémunération'

View File

@ -4,11 +4,11 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { H1 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { RootState } from '@/reducers/rootReducer'
import { useRelativeSitePaths } from '@/sitePaths'
import { useContext, useEffect } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { Route, Switch, useLocation } from 'react-router-dom'
import { Navigate } from 'react-router-dom-v5-compat'
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { TrackChapter } from '../../../ATInternetTracking'
import AutoEntrepreneur from './AutoEntrepreneur'
import DirectorStatus from './DirectorStatus'
@ -52,6 +52,7 @@ const useResetFollowingAnswers = () => {
export default function Créer() {
const sitePaths = useContext(SitePathsContext)
const relativeSitePaths = useRelativeSitePaths()
const location = useLocation()
useResetFollowingAnswers()
@ -68,29 +69,38 @@ export default function Créer() {
</H1>
<PreviousAnswers />
<FromBottom key={location.pathname}>
<Switch>
<Route path={sitePaths.créer.guideStatut.soleProprietorship}>
<SoleProprietorship />
</Route>
<Route path={sitePaths.créer.guideStatut.directorStatus}>
<DirectorStatus />
</Route>
<Route path={sitePaths.créer.guideStatut.autoEntrepreneur}>
<AutoEntrepreneur />
</Route>
<Route path={sitePaths.créer.guideStatut.multipleAssociates}>
<NumberOfAssociate />
</Route>
<Route path={sitePaths.créer.guideStatut.minorityDirector}>
<MinorityDirector />
</Route>
<Route path={sitePaths.créer.guideStatut.liste}>
<PickLegalStatus />
</Route>
<Route path={sitePaths.créer.guideStatut.index}>
<Navigate to={sitePaths.créer.guideStatut.liste} replace />
</Route>
</Switch>
<Routes>
<Route
path={relativeSitePaths.créer.guideStatut.soleProprietorship}
element={<SoleProprietorship />}
/>
<Route
path={relativeSitePaths.créer.guideStatut.directorStatus}
element={<DirectorStatus />}
/>
<Route
path={relativeSitePaths.créer.guideStatut.autoEntrepreneur}
element={<AutoEntrepreneur />}
/>
<Route
path={relativeSitePaths.créer.guideStatut.multipleAssociates}
element={<NumberOfAssociate />}
/>
<Route
path={relativeSitePaths.créer.guideStatut.minorityDirector}
element={<MinorityDirector />}
/>
<Route
path={relativeSitePaths.créer.guideStatut.liste}
element={<PickLegalStatus />}
/>
<Route
index
element={
<Navigate to={sitePaths.créer.guideStatut.liste} replace />
}
/>
</Routes>
</FromBottom>
</TrackChapter>
</>

View File

@ -95,10 +95,8 @@ export default function Créer() {
'créer.ressources.comparaison.title',
'Comparateur de régimes'
)}
to={{
pathname: sitePaths.simulateurs.comparaison,
state: { fromCréer: true },
}}
to={{ pathname: sitePaths.simulateurs.comparaison }}
state={{ fromCréer: true }}
ctaLabel={t('créer.ressources.comparaison.cta', 'Comparer')}
>
<Trans i18nKey="créer.ressources.comparaison.body">

View File

@ -1,35 +1,41 @@
import { ScrollToTop } from '@/components/utils/Scroll'
import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { useContext } from 'react'
import { Route, Switch, useLocation } from 'react-router-dom'
import { Route, Routes, useLocation } from 'react-router-dom'
import { TrackChapter } from '../../ATInternetTracking'
import { LANDING_LEGAL_STATUS_LIST } from '../../sitePaths'
import {
LANDING_LEGAL_STATUS_LIST,
useRelativeSitePaths,
} from '../../sitePaths'
import AfterRegistration from './AfterRegistration'
import CreationChecklist from './CreationChecklist'
import GuideStatut from './GuideStatut'
import Home from './Home'
export default function CreateMyCompany() {
const sitePaths = useContext(SitePathsContext)
const relativeSitePaths = useRelativeSitePaths()
const location = useLocation()
return (
<>
<ScrollToTop key={location.pathname} />
<TrackChapter chapter1="creer">
<Switch>
<Route exact path={sitePaths.créer.index} component={Home} />
<Routes>
<Route index element={<Home />} />
{LANDING_LEGAL_STATUS_LIST.map((statut) => (
<Route path={sitePaths.créer[statut]} key={statut}>
<CreationChecklist statut={statut} />
</Route>
<Route
key={statut}
path={relativeSitePaths.créer[statut]}
element={<CreationChecklist statut={statut} />}
/>
))}
<Route path={sitePaths.créer.après} component={AfterRegistration} />
<Route
path={sitePaths.créer.guideStatut.index}
component={GuideStatut}
path={relativeSitePaths.créer.après}
element={<AfterRegistration />}
/>
</Switch>
<Route
path={relativeSitePaths.créer.guideStatut.index + '/*'}
element={<GuideStatut />}
/>
</Routes>
</TrackChapter>
</>
)

View File

@ -19,7 +19,13 @@ import React, { ComponentType, useContext, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { Redirect, Route, useLocation, useParams } from 'react-router-dom'
import {
Navigate,
Route,
Routes,
useLocation,
useParams,
} from 'react-router-dom'
import styled from 'styled-components'
import { TrackPage } from '../ATInternetTracking'
import RuleLink from '../components/RuleLink'
@ -27,39 +33,39 @@ import RuleLink from '../components/RuleLink'
export default function MonEntrepriseRulePage() {
const engine = useEngine()
const documentationPath = useContext(SitePathsContext).documentation.index
const { pathname } = useLocation()
const location = useLocation()
const pathname = decodeURI(location?.pathname ?? '')
const documentationSitePaths = useMemo(
() => getDocumentationSiteMap({ engine, documentationPath }),
[engine, documentationPath]
)
if (pathname === '/documentation') {
return <DocumentationLanding />
}
if (pathname === '/documentation/dev') {
return <DocumentationRulesList />
}
if (!documentationSitePaths[pathname]) {
return <Redirect to="/404" />
}
return (
<FromBottom>
<TrackPage
chapter1="documentation"
name={documentationSitePaths[pathname]}
<Routes>
<Route index element={<DocumentationLanding />} />
<Route path="dev" element={<DocumentationRulesList />} />
<Route
path="*"
element={
!documentationSitePaths[pathname] ? (
<Navigate to="/404" replace />
) : (
<FromBottom>
<TrackPage
chapter1="documentation"
name={documentationSitePaths[pathname]}
/>
<ScrollToTop key={pathname} />
<Grid item md={10}>
<BackToSimulation />
<Spacing xl />
<DocumentationPageBody />
</Grid>
</FromBottom>
)
}
/>
<ScrollToTop key={pathname} />
<Grid item md={10}>
<BackToSimulation />
<Spacing xl />
<Route path={documentationPath + '/:name+'}>
<DocumentationPageBody />
</Route>
</Grid>
</FromBottom>
</Routes>
)
}
@ -67,13 +73,13 @@ function DocumentationPageBody() {
const engine = useEngine()
const documentationPath = useContext(SitePathsContext).documentation.index
const { i18n } = useTranslation()
const params = useParams<{ name: string }>()
const params = useParams<{ '*': string }>()
return (
<StyledDocumentation>
<RulePage
language={i18n.language as 'fr' | 'en'}
rulePath={params.name}
rulePath={params['*'] ?? ''}
engine={engine}
documentationPath={documentationPath}
renderers={{

View File

@ -1,6 +1,7 @@
import Route404 from '@/components/Route404'
import { IsEmbeded } from '@/components/utils/embeddedContext'
import { Helmet } from 'react-helmet-async'
import { Route, Switch } from 'react-router-dom'
import { Route, Routes } from 'react-router-dom'
import SimulateurPage from '../../components/PageData'
import useSimulatorsData from '../Simulateurs/metadata'
import IframeFooter from './IframeFooter'
@ -15,7 +16,7 @@ export default function Iframes() {
Our own link are handled in-app by the router, and aren't affected by this directive.
*/}
<base target="_parent" />
<Switch>
<Routes>
{Object.values(simulators)
.filter((el) => !!('iframePath' in el && el.iframePath))
.map(
@ -24,19 +25,20 @@ export default function Iframes() {
s.iframePath && (
<Route
key={s.iframePath}
path={`/iframes/${s.iframePath}`}
render={() => (
path={s.iframePath + '/*'}
element={
<>
<Helmet>
<link rel="canonical" href={s.path} />
</Helmet>
<SimulateurPage {...s} />
</>
)}
}
/>
)
)}
</Switch>
<Route path="*" element={<Route404 />} />
</Routes>
<IframeFooter />
</IsEmbeded>
)

View File

@ -18,7 +18,7 @@ import { getCookieValue } from '@/storage/readCookie'
import { useCallback, useContext, useEffect } from 'react'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { generatePath, useNavigate } from 'react-router-dom-v5-compat'
import { generatePath, useNavigate } from 'react-router-dom'
export default function SearchOrCreate() {
const sitePaths = useContext(SitePathsContext)

View File

@ -12,7 +12,7 @@ import { GenericButtonOrLinkProps, Link } from '@/design-system/typography/link'
import { Body } from '@/design-system/typography/paragraphs'
import { useFetchData } from '@/hooks/useFetchData'
import { useContext, useMemo } from 'react'
import { Navigate, useMatch, useNavigate } from 'react-router-dom-v5-compat'
import { Navigate, useMatch, useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { TrackPage } from '../../ATInternetTracking'
@ -47,7 +47,7 @@ export default function Nouveautés() {
`${sitePaths.nouveautés}/${slugify(data[index].name)}`
if (!slug || selectedRelease === -1) {
return <Navigate to={getPath(0)} />
return <Navigate to={getPath(0)} replace />
}
const releaseName = data[selectedRelease].name.toLowerCase()
@ -85,7 +85,9 @@ export default function Nouveautés() {
value={selectedRelease}
items={releasesWithId}
onSelectionChange={(id) => {
navigate(getPath(Number(id)))
if (id !== selectedRelease) {
navigate(getPath(Number(id)))
}
}}
>
{(release) => (

View File

@ -10,8 +10,7 @@ import { getValueFrom } from '@/utils'
import { formatValue } from 'publicodes'
import { useContext } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Redirect } from 'react-router-dom'
import { useParams } from 'react-router-dom-v5-compat'
import { Navigate, useParams } from 'react-router-dom'
import { TrackPage } from '../../../ATInternetTracking'
import { selectSeuilRevenus } from './actions'
import { getTranslatedActivité } from './activitésData'
@ -33,7 +32,7 @@ export default function Activité() {
const { state, dispatch } = useContext(StoreContext)
const activité = getTranslatedActivité(title, language)
if (state && !(title in state)) {
return <Redirect to={sitePaths.simulateurs.économieCollaborative.index} />
return <Navigate to={sitePaths.simulateurs.économieCollaborative.index} />
}
if (getValueFrom(activité, 'activités')) {

View File

@ -12,7 +12,7 @@ import { Body, SmallBody } from '@/design-system/typography/paragraphs'
import { useContext } from 'react'
import { Helmet } from 'react-helmet-async'
import { Trans, useTranslation } from 'react-i18next'
import { Navigate } from 'react-router-dom-v5-compat'
import { Navigate } from 'react-router-dom'
import { TrackPage } from '../../../ATInternetTracking'
import { ActiviteCard } from './ActiviteCard'
import illustration from './images/multitasking.svg'

View File

@ -3,7 +3,7 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Link } from '@/design-system/typography/link'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { Route, Routes } from 'react-router-dom-v5-compat'
import { Route, Routes } from 'react-router-dom'
import { TrackChapter } from '../../../ATInternetTracking'
import useSimulatorsData from '../metadata'
import Activité from './Activité'
@ -22,7 +22,10 @@ export default function ÉconomieCollaborative() {
return (
<TrackChapter chapter1="simulateurs" chapter2="economie_collaborative">
<div css="transform: translateY(2rem)">
<Link exact activeStyle={{ display: 'none' }} to={indexPath}>
<Link
style={({ isActive }) => (isActive ? { display: 'none' } : {})}
to={indexPath}
>
{' '}
<Trans i18nKey="économieCollaborative.retourAccueil">
Retour à la selection d'activités

View File

@ -12,7 +12,11 @@ import { DottedName as ExoCovidDottedNames } from 'exoneration-covid'
import { PublicodesExpression } from 'publicodes'
import { useCallback, useEffect } from 'react'
import { Trans } from 'react-i18next'
import { useLocation } from 'react-router'
import {
createSearchParams,
useLocation,
useSearchParams,
} from 'react-router-dom'
import { useExoCovidEngine } from '.'
import { FormulaireS1S1Bis } from './FormulaireS1S1Bis'
import { FormulaireS2 } from './FormulaireS2'
@ -25,7 +29,7 @@ const rootDottedNames = [
export const ExonérationCovid = () => {
const location = useLocation()
const searchParams = new URLSearchParams(location.search)
const [searchParams] = useSearchParams()
const params = Object.fromEntries(searchParams.entries()) as {
[key in typeof rootDottedNames[number]]?: string
}
@ -133,18 +137,18 @@ export const ExonérationCovid = () => {
isDisabled={!rootDottedNames.every((names) => situation[names])}
{...(rootDottedNames.every((names) => situation[names])
? {
to: () => {
rootDottedNames.forEach((key) =>
searchParams.append(
key,
situation[key]?.toString() ?? ''
to: {
pathname: location.pathname,
search: createSearchParams(
Object.fromEntries(
rootDottedNames
.filter((key) => situation[key])
.map((key) => [
key,
situation[key]?.toString() ?? '',
])
)
)
return {
pathname: location.pathname,
search: searchParams.toString(),
}
).toString(),
},
}
: null)}

View File

@ -168,12 +168,8 @@ export function SimulateurCard({
<Grid item xs={12} sm={6} md={6} lg={4}>
<SmallCard
icon={<Emoji emoji={icône} />}
to={{
state: fromGérer
? { fromGérer: true }
: { fromSimulateurs: true },
pathname: (isIframe && iframePath) || path,
}}
to={{ pathname: (isIframe && iframePath) || path }}
state={fromGérer ? { fromGérer: true } : { fromSimulateurs: true }}
title={
<h4>
{shortName} {tooltip && <InfoBulle>{tooltip}</InfoBulle>}
@ -187,12 +183,8 @@ export function SimulateurCard({
title={shortName}
icon={<Emoji emoji={icône} />}
ctaLabel={t('.cta', 'Lancer le simulateur')}
to={{
state: fromGérer
? { fromGérer: true }
: { fromSimulateurs: true },
pathname: (isIframe && iframePath) || path,
}}
to={{ pathname: (isIframe && iframePath) || path }}
state={fromGérer ? { fromGérer: true } : { fromSimulateurs: true }}
>
{meta?.description}
</Card>

View File

@ -25,10 +25,8 @@ export function SimulatorRessourceCard({
Accéder au simulateur
</Trans>
}
to={{
pathname: simulator.path,
state: { fromSimulateurs: true },
}}
to={{ pathname: simulator.path }}
state={{ fromSimulateurs: true }}
>
{simulator.meta?.description}
</Article>

View File

@ -5,10 +5,10 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Link } from '@/design-system/typography/link'
import { useContext, useEffect, useMemo } from 'react'
import { Trans } from 'react-i18next'
import { Route, Routes, useLocation } from 'react-router-dom-v5-compat'
import { Route, Routes, useLocation } from 'react-router-dom'
import SimulateurPage from '../../components/PageData'
import Home from './Home'
import useSimulatorsData from './metadata'
import SimulateurPage from '../../components/PageData'
export default function Simulateurs() {
const sitePaths = useContext(SitePathsContext)
@ -20,7 +20,7 @@ export default function Simulateurs() {
}>('navigation::simulateurs::locationState::v2', {})
useEffect(() => {
if (state) {
setLastState(state as any)
setLastState(state)
}
}, [setLastState, state])
const simulatorsData = useSimulatorsData()
@ -32,7 +32,7 @@ export default function Simulateurs() {
.map((s) => (
<Route
key={s.path}
path={s.path + '/*'}
path={s.path.replace(sitePaths.simulateurs.index, '') + '/*'}
element={<SimulateurPage {...s} />}
/>
)),
@ -60,7 +60,7 @@ export default function Simulateurs() {
)
) : null)}
<Routes>
<Route path={sitePaths.simulateurs.index} element={<Home />} />
<Route index element={<Home />} />
{simulatorRoutes}
</Routes>
</>

View File

@ -429,7 +429,7 @@ const metadataSrc = (t: TFunction<'translation', string>) => {
'Assistant à la déclaration de revenu pour les indépendants'
),
},
pathId: 'gérer.déclarationIndépendant.beta',
pathId: 'gérer.déclarationIndépendant.beta.index',
shortName: t(
'pages.gérer.declaration_revenu_indépendant.shortname',
'Assistant déclaration de revenu [beta]'

View File

@ -12,8 +12,8 @@ import { createContext, useContext, useMemo } from 'react'
import { TFunction, Trans, useTranslation } from 'react-i18next'
import { constructLocalizedSitePath, SitePathsType } from '../../sitePaths'
import Créer from '../Creer/Home'
import DéclarationRevenuIndépendant from '../gerer/declaration-revenu-independants'
import DéclarationChargeSocialeIndépendant from '../gerer/declaration-charges-sociales-independant'
import DéclarationRevenuIndépendant from '../gerer/declaration-revenu-independants'
import FormulaireMobilitéIndépendant from '../gerer/demande-mobilité'
import ArtisteAuteur from './ArtisteAuteur'
import AutoEntrepreneur from './AutoEntrepreneur'
@ -541,7 +541,7 @@ function getSimulatorsData({
'déclaration-revenu-indépendant-beta': {
...pureSimulatorsData['déclaration-revenu-indépendant-beta'],
component: DéclarationRevenuIndépendant,
path: sitePaths.gérer.déclarationIndépendant.beta,
path: sitePaths.gérer.déclarationIndépendant.beta.index,
},
'demande-mobilité': {
...pureSimulatorsData['demande-mobilité'],

View File

@ -11,7 +11,7 @@ import { useFetchData } from '@/hooks/useFetchData'
import { formatValue } from 'publicodes'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Trans } from 'react-i18next'
import { useSearchParams } from 'react-router-dom-v5-compat'
import { useSearchParams } from 'react-router-dom'
import { BrushProps } from 'recharts'
import styled from 'styled-components'
import { toAtString } from '../../ATInternetTracking'

View File

@ -16,12 +16,8 @@ export function ImpotSocieteCard() {
)}
icon={<Emoji emoji="🧾" />}
ctaLabel={t('gérer.choix.is.cta', 'Lancer le simulateur')}
to={{
pathname: sitePaths.simulateurs.is,
state: {
fromGérer: true,
},
}}
to={{ pathname: sitePaths.simulateurs.is }}
state={{ fromGérer: true }}
>
<Trans i18nKey="gérer.choix.is.body">
Calculez le montant de l'impôt sur les sociétés à partir de votre

View File

@ -170,7 +170,7 @@ export function DéclarationRevenuSection({ progress }: { progress: number }) {
<Button
size="XL"
isDisabled={progress !== 1}
to={sitePaths.gérer.déclarationIndépendant.cotisations}
to={sitePaths.gérer.déclarationIndépendant.beta.cotisations}
>
Continuer vers l'estimation des cotisations pour 2022
</Button>

View File

@ -172,7 +172,7 @@ export default function Accueil() {
<Spacing lg />
<Button
size="XL"
to={sitePaths.gérer.déclarationIndépendant.imposition}
to={sitePaths.gérer.déclarationIndépendant.beta.imposition}
>
Continuer avec cette entreprise
</Button>

View File

@ -341,7 +341,7 @@ function ResultSection() {
`}
>
<Button
to={sitePaths.gérer.déclarationIndépendant.déclaration}
to={sitePaths.gérer.déclarationIndépendant.beta.déclaration}
onPress={() => {
dispatchValue('non', 'DRI . déclaration revenus manuelle')
}}
@ -414,7 +414,7 @@ function ResultSection() {
<Button
light
size="XS"
to={sitePaths.gérer.déclarationIndépendant.déclaration}
to={sitePaths.gérer.déclarationIndépendant.beta.déclaration}
onPress={() => {
dispatchValue('oui', 'DRI . déclaration revenus manuelle')
}}

View File

@ -10,10 +10,11 @@ import { Spacing } from '@/design-system/layout'
import { Strong } from '@/design-system/typography'
import { H3 } from '@/design-system/typography/heading'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { useRelativeSitePaths } from '@/sitePaths'
import { omit } from '@/utils'
import { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Redirect, Route, Switch } from 'react-router'
import { Navigate, Route, Routes } from 'react-router-dom'
import Cotisations from './cotisations'
import Déclaration, { useObjectifs as useStep3Objectifs } from './declaration'
import Entreprise, { OBJECTIFS as Step1Objectifs } from './entreprise'
@ -24,7 +25,7 @@ import config from './_config.yaml'
export default function AideDéclarationIndépendant() {
const sitePaths = useContext(SitePathsContext)
useSimulationConfig(config, {
path: sitePaths.gérer.déclarationIndépendant.beta,
path: sitePaths.gérer.déclarationIndépendant.beta.index,
autoloadLastSimulation: true,
})
const steps = useSteps()
@ -62,21 +63,20 @@ export default function AideDéclarationIndépendant() {
))}
</Stepper>
</div>
<Switch>
<Routes>
{steps.map(
(step) =>
step.page &&
!step.isDisabled && (
<Route
key={step.to}
path={step.to}
exact
component={step.page}
/>
<Route key={step.to} path={step.to} element={<step.page />} />
)
)}
{defaultCurrentStep && <Redirect to={defaultCurrentStep.to} />}
</Switch>
<Route
path="*"
element={
<Navigate to={(defaultCurrentStep || steps[0]).to} replace />
}
/>
</Routes>
<Spacing xl />
</Condition>
</>
@ -84,7 +84,7 @@ export default function AideDéclarationIndépendant() {
}
function useSteps() {
const sitePaths = useContext(SitePathsContext).gérer.déclarationIndépendant
const sitePaths = useRelativeSitePaths().gérer.déclarationIndépendant.beta
const { t } = useTranslation()
const step1Progress = useProgress(Step1Objectifs)
const step2Progress = useProgress(Step2Objectifs)

View File

@ -10,6 +10,7 @@ import {
WhenApplicable,
WhenNotApplicable,
} from '@/components/EngineValue'
import PageData from '@/components/PageData'
import PageHeader from '@/components/PageHeader'
import { PlaceDesEntreprisesButton } from '@/components/PlaceDesEntreprises'
import { FromTop } from '@/components/ui/animate'
@ -24,11 +25,11 @@ import { Container, Grid, Spacing } from '@/design-system/layout'
import { Strong } from '@/design-system/typography'
import { H2, H4 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { Li, Ul } from '@/design-system/typography/list'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { useQuestionList } from '@/hooks/useQuestionList'
import { useSetEntreprise } from '@/hooks/useSetEntreprise'
import { companySituationSelector } from '@/selectors/simulationSelectors'
import { useRelativeSitePaths } from '@/sitePaths'
import { evaluateQuestion } from '@/utils'
import { useOverlayTriggerState } from '@react-stately/overlays'
import { DottedName } from 'modele-social'
@ -39,16 +40,14 @@ import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import {
generatePath,
Redirect,
Navigate,
Route,
Switch,
Routes,
useLocation,
useParams,
useRouteMatch,
} from 'react-router'
} from 'react-router-dom'
import styled from 'styled-components'
import { TrackChapter, TrackPage } from '../../ATInternetTracking'
import PageData from '../../components/PageData'
import { SimulateurCard } from '../Simulateurs/Home'
import useSimulatorsData, { SimulatorData } from '../Simulateurs/metadata'
import Embaucher from './embaucher'
@ -63,29 +62,42 @@ import { SecuriteSocialeCard } from './_components/SecuriteSocialeCard'
export default function Gérer() {
const sitePaths = useContext(SitePathsContext)
const relativeSitePaths = useRelativeSitePaths()
const location = useLocation()
const simulateurs = useSimulatorsData()
const showLink = !useRouteMatch({
path: [sitePaths.gérer.index, sitePaths.gérer.entreprise],
exact: true,
})
const back = (
<Link to={sitePaths.gérer.index}>
<Trans>Retour à mon activité</Trans>
</Link>
)
return (
<>
<ScrollToTop key={location.pathname} />
{showLink && (
<Link to={sitePaths.gérer.index}>
<Trans>Retour à mon activité</Trans>
</Link>
)}
<TrackChapter chapter1="gerer">
<Switch>
<Routes>
<Route index element={<Home />} />
<Route path={relativeSitePaths.gérer.entreprise} element={<Home />} />
<Route
path={sitePaths.gérer.sécuritéSociale}
component={SocialSecurity}
path={relativeSitePaths.gérer.sécuritéSociale}
element={
<>
{back}
<SocialSecurity />
</>
}
/>
<Route
path={relativeSitePaths.gérer.embaucher}
element={
<>
{back}
<Embaucher />
</>
}
/>
<Route path={sitePaths.gérer.embaucher} component={Embaucher} />
{[
simulateurs['déclaration-charges-sociales-indépendant'],
simulateurs['déclaration-revenu-indépendant-beta'],
@ -94,16 +106,16 @@ export default function Gérer() {
].map((p) => (
<Route
key={p.shortName}
path={p.path}
render={() => <PageData {...p} />}
path={p.path.replace(sitePaths.gérer.index, '') + '/*'}
element={
<>
{back}
<PageData {...p} />
</>
}
/>
))}
<Route
exact
path={[sitePaths.gérer.index, sitePaths.gérer.entreprise]}
component={Home}
/>
</Switch>
</Routes>
</TrackChapter>
</>
)
@ -208,7 +220,7 @@ function Home() {
gérerPath &&
(param == null || (entreprise?.siren && entreprise.siren !== param))
) {
return <Redirect to={gérerPath} />
return <Navigate to={gérerPath} />
}
if (
@ -216,7 +228,7 @@ function Home() {
(param && entrepriseNotFound) ||
(entreprise && !overwrite && !engineSiren)
) {
return <Redirect to={sitePaths.index} />
return <Navigate to={sitePaths.index} />
}
return (

View File

@ -10,7 +10,7 @@ import urssafLogo from '@/images/Urssaf.svg'
import { useEffect, useRef, useState } from 'react'
import { HexColorPicker } from 'react-colorful'
import { Trans, useTranslation } from 'react-i18next'
import { useHref, useSearchParams } from 'react-router-dom-v5-compat'
import { useHref, useSearchParams } from 'react-router-dom'
import styled from 'styled-components'
import { TrackPage } from '../../ATInternetTracking'
import useSimulatorsData, { SimulatorData } from '../Simulateurs/metadata'

View File

@ -4,9 +4,10 @@ import { SitePathsContext } from '@/components/utils/SitePathsContext'
import { Banner, InnerBanner } from '@/design-system/banner'
import { Link } from '@/design-system/typography/link'
import { useFetchData } from '@/hooks/useFetchData'
import { useRelativeSitePaths } from '@/sitePaths'
import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { Route, Routes, useLocation } from 'react-router-dom-v5-compat'
import { Route, Routes, useLocation } from 'react-router-dom'
import { TrackChapter } from '../../ATInternetTracking'
import API from './API'
import Iframe from './Iframe'
@ -22,6 +23,7 @@ type JobOffer = {
export default function Integration() {
const sitePaths = useContext(SitePathsContext)
const relativeSitePaths = useRelativeSitePaths()
const { pathname } = useLocation()
const { data: jobOffers } = useFetchData<JobOffer[]>('/data/job-offers.json')
const openJobOffer = jobOffers?.[0]
@ -52,12 +54,18 @@ export default function Integration() {
</Banner>
)}
<Routes>
<Route path={sitePaths.développeur.index} element={<Options />} />
<Route path={sitePaths.développeur.iframe} element={<Iframe />} />
<Route path={sitePaths.développeur.library} element={<Library />} />
<Route path={sitePaths.développeur.api} element={<API />} />
<Route index element={<Options />} />
<Route path={relativeSitePaths.développeur.api} element={<API />} />
<Route
path={sitePaths.développeur.spreadsheet}
path={relativeSitePaths.développeur.iframe}
element={<Iframe />}
/>
<Route
path={relativeSitePaths.développeur.library}
element={<Library />}
/>
<Route
path={relativeSitePaths.développeur.spreadsheet}
element={<Spreadsheet />}
/>
</Routes>

View File

@ -1,5 +1,6 @@
import { MetadataSrc } from 'pages/Simulateurs/metadata-src'
import { LegalStatus } from '@/selectors/companyStatusSelectors'
import { useTranslation } from 'react-i18next'
export const LANDING_LEGAL_STATUS_LIST: Array<LegalStatus> = [
'EI',
@ -13,85 +14,89 @@ export const LANDING_LEGAL_STATUS_LIST: Array<LegalStatus> = [
'SA',
]
const status = Object.fromEntries(
LANDING_LEGAL_STATUS_LIST.map((statut) => [statut, statut])
) as { [statut in LegalStatus]: statut }
const rawSitePathsFr = {
index: '',
créer: {
index: '/créer',
...(Object.fromEntries(
LANDING_LEGAL_STATUS_LIST.map((statut) => [statut, `/${statut}`])
) as { [statut in LegalStatus]: string }),
après: '/après-la-création',
index: 'créer',
...status,
après: 'après-la-création',
guideStatut: {
index: '/statut-juridique',
liste: '/liste',
soleProprietorship: '/responsabilité',
directorStatus: '/dirigeant',
autoEntrepreneur: '/auto-entrepreneur-ou-entreprise-individuelle',
multipleAssociates: '/nombre-associés',
minorityDirector: '/gérant-majoritaire-ou-minoritaire',
index: 'statut-juridique',
liste: 'liste',
soleProprietorship: 'responsabilité',
directorStatus: 'dirigeant',
autoEntrepreneur: 'auto-entrepreneur-ou-entreprise-individuelle',
multipleAssociates: 'nombre-associés',
minorityDirector: 'gérant-majoritaire-ou-minoritaire',
},
},
gérer: {
index: '/gérer',
entreprise: '/:entreprise',
embaucher: '/embaucher',
sécuritéSociale: '/sécurité-sociale',
index: 'gérer',
entreprise: ':entreprise',
embaucher: 'embaucher',
sécuritéSociale: 'sécurité-sociale',
'déclaration-charges-sociales-indépendant':
'/declaration-charges-sociales-independant',
'declaration-charges-sociales-independant',
déclarationIndépendant: {
index: '/aide-declaration-independants',
beta: '/beta',
entreprise: '/beta/entreprise',
imposition: '/beta/imposition',
déclaration: '/beta/declaration',
cotisations: '/beta/cotisations',
index: 'aide-declaration-independants',
beta: {
index: 'beta',
entreprise: 'entreprise',
imposition: 'imposition',
déclaration: 'declaration',
cotisations: 'cotisations',
},
},
formulaireMobilité: '/demande-mobilité',
formulaireMobilité: 'demande-mobilité',
},
simulateurs: {
index: '/simulateurs',
'auto-entrepreneur': '/auto-entrepreneur',
'entreprise-individuelle': '/entreprise-individuelle',
eirl: '/eirl',
sasu: '/sasu',
eurl: '/eurl',
indépendant: '/indépendant',
comparaison: '/comparaison-régimes-sociaux',
pamc: '/pamc',
salarié: '/salaire-brut-net',
'artiste-auteur': '/artiste-auteur',
index: 'simulateurs',
'auto-entrepreneur': 'auto-entrepreneur',
'entreprise-individuelle': 'entreprise-individuelle',
eirl: 'eirl',
sasu: 'sasu',
eurl: 'eurl',
indépendant: 'indépendant',
comparaison: 'comparaison-régimes-sociaux',
pamc: 'pamc',
salarié: 'salaire-brut-net',
'artiste-auteur': 'artiste-auteur',
'profession-libérale': {
index: '/profession-liberale',
médecin: '/medecin',
pharmacien: '/pharmacien',
auxiliaire: '/auxiliaire-medical',
'chirurgien-dentiste': '/chirurgien-dentiste',
'sage-femme': '/sage-femme',
avocat: '/avocat',
'expert-comptable': '/expert-comptable',
index: 'profession-liberale',
médecin: 'medecin',
pharmacien: 'pharmacien',
auxiliaire: 'auxiliaire-medical',
'chirurgien-dentiste': 'chirurgien-dentiste',
'sage-femme': 'sage-femme',
avocat: 'avocat',
'expert-comptable': 'expert-comptable',
},
'chômage-partiel': '/chômage-partiel',
'chômage-partiel': 'chômage-partiel',
économieCollaborative: {
index: '/économie-collaborative',
votreSituation: '/votre-situation',
index: 'économie-collaborative',
votreSituation: 'votre-situation',
},
is: '/impot-societe',
dividendes: '/dividendes',
'exonération-covid': '/exonération-covid',
is: 'impot-societe',
dividendes: 'dividendes',
'exonération-covid': 'exonération-covid',
},
nouveautés: '/nouveautés',
stats: '/stats',
accessibilité: '/accessibilité',
budget: '/budget',
nouveautés: 'nouveautés',
stats: 'stats',
accessibilité: 'accessibilité',
budget: 'budget',
développeur: {
index: '/développeur',
iframe: '/iframe',
library: '/bibliothèque-de-calcul',
api: '/api',
spreadsheet: '/tableur',
index: 'développeur',
iframe: 'iframe',
library: 'bibliothèque-de-calcul',
api: 'api',
spreadsheet: 'tableur',
},
documentation: {
index: '/documentation',
index: 'documentation',
},
} as const
@ -99,80 +104,83 @@ const rawSitePathsEn = {
...rawSitePathsFr,
créer: {
...rawSitePathsFr.créer,
index: '/create',
après: '/after-registration',
index: 'create',
après: 'after-registration',
guideStatut: {
index: '/legal-status',
liste: '/list',
soleProprietorship: '/liability',
directorStatus: '/director',
autoEntrepreneur: '/auto-entrepreneur',
multipleAssociates: '/multiple-associates',
minorityDirector: '/chairman-or-managing-director',
index: 'legal-status',
liste: 'list',
soleProprietorship: 'liability',
directorStatus: 'director',
autoEntrepreneur: 'auto-entrepreneur',
multipleAssociates: 'multiple-associates',
minorityDirector: 'chairman-or-managing-director',
},
},
gérer: {
index: '/manage',
entreprise: '/:entreprise',
embaucher: '/hiring',
sécuritéSociale: '/social-security',
index: 'manage',
entreprise: ':entreprise',
embaucher: 'hiring',
sécuritéSociale: 'social-security',
'déclaration-charges-sociales-indépendant':
'/declaration-social-charges-independent',
'declaration-social-charges-independent',
déclarationIndépendant: {
index: '/declaration-aid-independent',
beta: '/beta',
imposition: '/beta/taxation',
entreprise: '/beta/company',
déclaration: '/beta/declaration',
cotisations: '/beta/contributions',
index: 'declaration-aid-independent',
beta: {
index: 'beta',
imposition: 'taxation',
entreprise: 'company',
déclaration: 'declaration',
cotisations: 'contributions',
},
},
formulaireMobilité: '/posting-demand',
formulaireMobilité: 'posting-demand',
},
simulateurs: {
index: '/calculators',
indépendant: '/independant',
'entreprise-individuelle': '/sole-proprietorship',
'auto-entrepreneur': '/auto-entrepreneur',
eirl: '/eirl',
sasu: '/sasu',
eurl: '/eurl',
pamc: '/pamc',
comparaison: '/social-scheme-comparaison',
salarié: '/salary',
'artiste-auteur': '/artist-author',
'chômage-partiel': '/partial-unemployement',
index: 'calculators',
indépendant: 'independant',
'entreprise-individuelle': 'sole-proprietorship',
'auto-entrepreneur': 'auto-entrepreneur',
eirl: 'eirl',
sasu: 'sasu',
eurl: 'eurl',
pamc: 'pamc',
comparaison: 'social-scheme-comparaison',
salarié: 'salary',
'artiste-auteur': 'artist-author',
'chômage-partiel': 'partial-unemployement',
'profession-libérale': {
index: '/liberal-profession',
médecin: '/doctor',
pharmacien: '/pharmacist',
auxiliaire: '/medical-auxiliary',
'chirurgien-dentiste': '/dental-surgeon',
'sage-femme': '/midwife',
avocat: '/lawyer',
'expert-comptable': '/accountant',
index: 'liberal-profession',
médecin: 'doctor',
pharmacien: 'pharmacist',
auxiliaire: 'medical-auxiliary',
'chirurgien-dentiste': 'dental-surgeon',
'sage-femme': 'midwife',
avocat: 'lawyer',
'expert-comptable': 'accountant',
},
économieCollaborative: {
index: '/sharing-economy',
votreSituation: '/your-situation',
index: 'sharing-economy',
votreSituation: 'your-situation',
},
is: '/corporate-tax',
dividendes: '/dividends',
'exonération-covid': '/exoneration-covid',
is: 'corporate-tax',
dividendes: 'dividends',
'exonération-covid': 'exoneration-covid',
},
nouveautés: '/news',
accessibilité: '/accessibility',
nouveautés: 'news',
accessibilité: 'accessibility',
développeur: {
...rawSitePathsFr.développeur,
index: '/developer',
library: '/library',
api: '/api',
spreadsheet: '/spreadsheet',
index: 'developer',
library: 'library',
api: 'api',
spreadsheet: 'spreadsheet',
spreadsheetx: 'spreadsheet',
},
} as const
/**
* Le but des types suivants est d'obtenir un typage statique des chaînes de caractères
* comme "simulateurs.auto-entrepreneur" utilisés comme identifiants des routes (via les pathId dans metadat-src.ts).
* comme "simulateurs.auto-entrepreneur" utilisés comme identifiants des routes (via les pathId dans metadata-src.ts).
* Cela permet de ne pas avoir de faute dans les clés comme 'aide-embauche' au lieu de 'aides-embauche'
*/
@ -203,18 +211,47 @@ const checkedSitePathsEn: RequiredPath & typeof rawSitePathsEn = rawSitePathsEn
type SitePathsFr = typeof checkedSitePathsFr
type SitePathsEn = typeof checkedSitePathsEn
type GenericSitePath = { [key: string]: string | GenericSitePath }
const encodeRelativeSitePaths = <T extends GenericSitePath>(base: T): T => {
const sitepaths = Object.entries(base).reduce(
(obj, [key, val]) => ({
...obj,
[key]:
typeof val === 'string' ? encodeURI(val) : encodeRelativeSitePaths(val),
}),
{} as T
)
return sitepaths
}
const encodedRelativeSitePaths = encodeRelativeSitePaths({
fr: rawSitePathsFr,
en: rawSitePathsEn,
})
export const useRelativeSitePaths = () => {
const { language } = useTranslation().i18n
return encodedRelativeSitePaths[language as 'fr' | 'en']
}
export type RelativeSitePath = ReturnType<typeof useRelativeSitePaths>
type SitePath = { [key: string]: string | SitePath } & { index: string }
type SitePathBuilt<T extends SitePath, Root extends string = ''> = {
[K in keyof T]: T[K] extends string
? K extends 'index'
? `${Root}${T[K]}`
? `${Root}${T[K]}` extends ''
? '/'
: `${Root}${T[K]}`
: T extends { index: string }
? `${Root}${T['index']}${T[K]}`
? `${Root}${T['index']}/${T[K]}`
: `${Root}${T[K]}`
: SitePathBuilt<
T[K] extends SitePath ? T[K] : never,
T extends { index: string } ? `${Root}${T['index']}` : `${Root}`
T extends { index: string } ? `${Root}${T['index']}/` : `${Root}`
>
}
@ -229,8 +266,8 @@ function constructSitePaths<T extends SitePath>(
entries.map(([k, value]) => [
k,
typeof value === 'string'
? root + (k === 'index' ? value : index + value)
: constructSitePaths(value, root + index),
? encodeURI(root + (k === 'index' ? value : index + '/' + value)) || '/'
: constructSitePaths(value, root + index + '/'),
])
) as SitePathBuilt<T>
}

168
yarn.lock
View File

@ -2880,7 +2880,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.4, @babel/runtime@npm:^7.17.0, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.4, @babel/runtime@npm:^7.17.0, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
version: 7.17.8
resolution: "@babel/runtime@npm:7.17.8"
dependencies:
@ -8210,13 +8210,6 @@ __metadata:
languageName: node
linkType: hard
"@types/history@npm:^4.7.11":
version: 4.7.11
resolution: "@types/history@npm:4.7.11"
checksum: c92e2ba407dcab0581a9afdf98f533aa41b61a71133420a6d92b1ca9839f741ab1f9395b17454ba5b88cb86020b70b22d74a1950ccfbdfd9beeaa5459fdc3464
languageName: node
linkType: hard
"@types/history@npm:^5.0.0":
version: 5.0.0
resolution: "@types/history@npm:5.0.0"
@ -8614,27 +8607,6 @@ __metadata:
languageName: node
linkType: hard
"@types/react-router-dom@npm:^5.3.3":
version: 5.3.3
resolution: "@types/react-router-dom@npm:5.3.3"
dependencies:
"@types/history": ^4.7.11
"@types/react": "*"
"@types/react-router": "*"
checksum: 28c4ea48909803c414bf5a08502acbb8ba414669b4b43bb51297c05fe5addc4df0b8fd00e0a9d1e3535ec4073ef38aaafac2c4a2b95b787167d113bc059beff3
languageName: node
linkType: hard
"@types/react-router@npm:*, @types/react-router@npm:^5.1.18":
version: 5.1.18
resolution: "@types/react-router@npm:5.1.18"
dependencies:
"@types/history": ^4.7.11
"@types/react": "*"
checksum: f08b37ee822f9f9ff904ffd0778fe2bb7c717ed3ee311610382ee024d02a35169bd3301ecde863cac21aae8fdee919501e8ea22bad0260c02c10cfbdba5c71be
languageName: node
linkType: hard
"@types/react@npm:*, @types/react@npm:^17.0.0":
version: 17.0.43
resolution: "@types/react@npm:17.0.43"
@ -17216,7 +17188,7 @@ __metadata:
languageName: node
linkType: hard
"history@npm:*, history@npm:^5.2.0, history@npm:^5.3.0":
"history@npm:*, history@npm:^5.2.0":
version: 5.3.0
resolution: "history@npm:5.3.0"
dependencies:
@ -17234,20 +17206,6 @@ __metadata:
languageName: node
linkType: hard
"history@npm:^4.9.0":
version: 4.10.1
resolution: "history@npm:4.10.1"
dependencies:
"@babel/runtime": ^7.1.2
loose-envify: ^1.2.0
resolve-pathname: ^3.0.0
tiny-invariant: ^1.0.2
tiny-warning: ^1.0.0
value-equal: ^1.0.1
checksum: addd84bc4683929bae4400419b5af132ff4e4e9b311a0d4e224579ea8e184a6b80d7f72c55927e4fa117f69076a9e47ce082d8d0b422f1a9ddac7991490ca1d0
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
@ -17259,7 +17217,7 @@ __metadata:
languageName: node
linkType: hard
"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2":
"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
dependencies:
@ -18732,13 +18690,6 @@ __metadata:
languageName: node
linkType: hard
"isarray@npm:0.0.1":
version: 0.0.1
resolution: "isarray@npm:0.0.1"
checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4
languageName: node
linkType: hard
"isarray@npm:1.0.0, isarray@npm:^1.0.0, isarray@npm:~1.0.0":
version: 1.0.0
resolution: "isarray@npm:1.0.0"
@ -20027,7 +19978,7 @@ __metadata:
languageName: node
linkType: hard
"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0":
"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
@ -20659,19 +20610,6 @@ __metadata:
languageName: node
linkType: hard
"mini-create-react-context@npm:^0.4.0":
version: 0.4.1
resolution: "mini-create-react-context@npm:0.4.1"
dependencies:
"@babel/runtime": ^7.12.1
tiny-warning: ^1.0.3
peerDependencies:
prop-types: ^15.0.0
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
checksum: f8cb2c7738aac355fe9ce7e8425f371b7fa90daddd5133edda4ccfdc18c49043b2ec04be6f3abf09b60a0f52549d54f158d5bfd81cdfb1a658531e5b9fe7bc6a
languageName: node
linkType: hard
"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1":
version: 1.0.1
resolution: "minimalistic-assert@npm:1.0.1"
@ -22572,15 +22510,6 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:^1.7.0":
version: 1.8.0
resolution: "path-to-regexp@npm:1.8.0"
dependencies:
isarray: 0.0.1
checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd
languageName: node
linkType: hard
"path-to-regexp@npm:^6.1.0":
version: 6.2.1
resolution: "path-to-regexp@npm:6.2.1"
@ -23710,7 +23639,7 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0":
"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
@ -23785,38 +23714,7 @@ __metadata:
languageName: node
linkType: hard
"react-router-dom-v5-compat@npm:^6.3.0":
version: 6.3.0
resolution: "react-router-dom-v5-compat@npm:6.3.0"
dependencies:
history: ^5.3.0
react-router: 6.3.0
peerDependencies:
react: ">=16.8"
react-dom: ">=16.8"
react-router-dom: 4 || 5
checksum: ee82b48078bac91cea3b8c499954cbf13f9261daef87e5032695946652b594b2f0590d279448ac59dd26a1c0d208a7c53d3aef2e97dcd0335f4af82df228f197
languageName: node
linkType: hard
"react-router-dom@npm:^5.3.3":
version: 5.3.3
resolution: "react-router-dom@npm:5.3.3"
dependencies:
"@babel/runtime": ^7.12.13
history: ^4.9.0
loose-envify: ^1.3.1
prop-types: ^15.6.2
react-router: 5.3.3
tiny-invariant: ^1.0.2
tiny-warning: ^1.0.0
peerDependencies:
react: ">=15"
checksum: e1998918e391611f09b967bce0cb88bc9794aa3d8dc5f86453467a1226ae2ace648a1f401f5282f19c84a3a61fa6a3207e2a6fdfe8c8efae0b255244631febeb
languageName: node
linkType: hard
"react-router-dom@npm:^6.0.0":
"react-router-dom@npm:^6.0.0, react-router-dom@npm:^6.3.0":
version: 6.3.0
resolution: "react-router-dom@npm:6.3.0"
dependencies:
@ -23829,26 +23727,6 @@ __metadata:
languageName: node
linkType: hard
"react-router@npm:5.3.3, react-router@npm:^5.3.3":
version: 5.3.3
resolution: "react-router@npm:5.3.3"
dependencies:
"@babel/runtime": ^7.12.13
history: ^4.9.0
hoist-non-react-statics: ^3.1.0
loose-envify: ^1.3.1
mini-create-react-context: ^0.4.0
path-to-regexp: ^1.7.0
prop-types: ^15.6.2
react-is: ^16.6.0
tiny-invariant: ^1.0.2
tiny-warning: ^1.0.0
peerDependencies:
react: ">=15"
checksum: 52a9f28fa97577fda18a8ed2922b658704eafe873e444fe07202640d55d9e81e67c03efd2b2a5b80e3a80e8be8352df826a227ce5f42b33b91bef853c74d4841
languageName: node
linkType: hard
"react-router@npm:6.3.0, react-router@npm:^6.0.0":
version: 6.3.0
resolution: "react-router@npm:6.3.0"
@ -24492,13 +24370,6 @@ __metadata:
languageName: node
linkType: hard
"resolve-pathname@npm:^3.0.0":
version: 3.0.0
resolution: "resolve-pathname@npm:3.0.0"
checksum: 6147241ba42c423dbe83cb067a2b4af4f60908c3af57e1ea567729cc71416c089737fe2a73e9e79e7a60f00f66c91e4b45ad0d37cd4be2d43fec44963ef14368
languageName: node
linkType: hard
"resolve-url@npm:^0.2.1":
version: 0.2.1
resolution: "resolve-url@npm:0.2.1"
@ -25296,8 +25167,6 @@ __metadata:
"@types/react-dom": ^17.0.9
"@types/react-instantsearch-dom": ^6.10.1
"@types/react-redux": ^7.1.23
"@types/react-router": ^5.1.18
"@types/react-router-dom": ^5.3.3
"@types/recharts": ^1.8.16
"@types/serve-static": ^1.13.10
"@types/styled-components": ^5.1.24
@ -25328,9 +25197,7 @@ __metadata:
react-instantsearch: ^6.11.2
react-instantsearch-dom: ^6.11.2
react-redux: ^7.2.8
react-router: ^5.3.3
react-router-dom: ^5.3.3
react-router-dom-v5-compat: ^6.3.0
react-router-dom: ^6.3.0
react-signature-pad-wrapper: ^1.2.11
react-spring: ^9.3.1
react-use-measure: ^2.0.4
@ -26708,20 +26575,6 @@ __metadata:
languageName: node
linkType: hard
"tiny-invariant@npm:^1.0.2":
version: 1.2.0
resolution: "tiny-invariant@npm:1.2.0"
checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade
languageName: node
linkType: hard
"tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.3":
version: 1.0.3
resolution: "tiny-warning@npm:1.0.3"
checksum: da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71
languageName: node
linkType: hard
"tinypool@npm:^0.1.2, tinypool@npm:^0.1.3":
version: 0.1.3
resolution: "tinypool@npm:0.1.3"
@ -27862,13 +27715,6 @@ __metadata:
languageName: node
linkType: hard
"value-equal@npm:^1.0.1":
version: 1.0.1
resolution: "value-equal@npm:1.0.1"
checksum: bb7ae1facc76b5cf8071aeb6c13d284d023fdb370478d10a5d64508e0e6e53bb459c4bbe34258df29d82e6f561f874f0105eba38de0e61fe9edd0bdce07a77a2
languageName: node
linkType: hard
"value-or-function@npm:^3.0.0":
version: 3.0.0
resolution: "value-or-function@npm:3.0.0"