diff --git a/.eslintrc.json b/.eslintrc.json index c8557b531..a8db3a548 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,9 +19,10 @@ "exclude": ["node_modules", "cypress/**/*"] }, "globals": { - "React": "writable", - "NodeJS": "writable", - "JSX": "writable" + "React": "readonly", + "NodeJS": "readonly", + "JSX": "readonly", + "jest": "readonly" }, "rules": { "react/no-unescaped-entities": "off", diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml new file mode 100644 index 000000000..f221ba906 --- /dev/null +++ b/.github/workflows/run-unit-tests.yaml @@ -0,0 +1,29 @@ +name: Run Unit Tests + +on: + push: + branches: + - main + pull_request: + branches: + - '**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run unit tests + run: yarn test --coverage diff --git a/.gitignore b/.gitignore index b837574ee..c955e8605 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ dist # Sentry Config File .env.sentry-build-plugin + +coverage diff --git a/.vscode/settings.json b/.vscode/settings.json index 53c193244..a0cdfec52 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,6 @@ "activityBar.background": "#296c36", "titleBar.activeBackground": "#296c36", "titleBar.activeForeground": "#FAF9FD" - } + }, + "editor.formatOnSave": true } diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 000000000..ce6cf042c --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,38 @@ +/** + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import type { Config } from 'jest' + +const config: Config = { + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: false, + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'v8', + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + '@/(.*)': '/src/$1', + }, + + // The test environment that will be used for testing + testEnvironment: 'jsdom', + + // The regexp pattern or array of patterns that Jest uses to detect test files + testRegex: '.spec.ts$', + + // A map from regular expressions to paths to transformers + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, +} + +export default config diff --git a/next.config.js b/next.config.js index 5307d3e00..3b79361b0 100644 --- a/next.config.js +++ b/next.config.js @@ -1,12 +1,9 @@ //@ts-check +/* eslint-disable @typescript-eslint/no-var-requires */ const withMDX = require('@next/mdx')({ extension: /\.mdx$/, }) -const withBundleAnalyzer = require('@next/bundle-analyzer')({ - enabled: process.env.ANALYZE === 'true', -}) - const { withSentryConfig } = require('@sentry/nextjs') const redirects = require('./config/redirects.js') @@ -18,13 +15,14 @@ const nextConfig = { pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], reactStrictMode: true, images: { + // @ts-expect-error remotePatterns is not typed remotePatterns: remoteImagesPatterns, }, async redirects() { return redirects }, - webpack: (config, { dev }) => { - if (config.cache && !dev) { + webpack: (config) => { + if (config.cache) { config.cache = Object.freeze({ type: 'memory', }) @@ -47,6 +45,7 @@ const nextConfig = { '/actions/plus': ['public/images/blog', 'public/NGC_Kit.diffusion.zip'], '/sitemap.xml': ['public/images/blog', 'public/NGC_Kit.diffusion.zip'], }, + optimizePackageImports: ['@incubateur-ademe/nosgestesclimat'], webpackBuildWorker: true, turbo: { rules: { @@ -90,7 +89,7 @@ const sentryConfig = [ }, ] -module.exports = withSentryConfig( - withBundleAnalyzer(withMDX(nextConfig)), - ...sentryConfig -) +module.exports = + process.env.NODE_ENV !== 'development' + ? withSentryConfig(withMDX(nextConfig), ...sentryConfig) + : nextConfig diff --git a/package.json b/package.json index 3a50c5da8..de199fe3a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nosgestesclimat-site-nextjs", "license": "MIT", - "version": "1.5.7", + "version": "2.0.0", "description": "The leading open source climate footprint calculator", "repository": { "type": "git", @@ -11,6 +11,7 @@ "node": "20" }, "scripts": { + "test": "jest", "dev": "next dev", "dev:turbo": "next dev --turbo", "build": "next build", @@ -32,17 +33,20 @@ "dependencies": { "@babel/runtime": "^7.23.1", "@incubateur-ademe/legal-pages-react": "^0.2.0", - "@incubateur-ademe/nosgestesclimat": "3.0.0-rc.3", + "@incubateur-ademe/nosgestesclimat": "^3.1.0", "@mdx-js/loader": "^3.0.0", "@mdx-js/react": "^3.0.0", "@next/bundle-analyzer": "^14.1.0", - "@next/mdx": "^14.0.2", + "@next/mdx": "^14.2.7", "@publicodes/react-ui": "^1.3.3", "@publicodes/tools": "^1.2.0", "@sentry/nextjs": "^8", "@sentry/react": "^8.10.0", "@socialgouv/react-departements": "^3.0.0", + "@swc/core": "^1.7.12", + "@swc/jest": "^0.2.36", "@tanstack/react-query": "^5.28.4", + "@types/jest": "^29.5.12", "axios": "^1.6.8", "dayjs": "^1.11.10", "framer-motion": "^11.0.14", @@ -53,8 +57,9 @@ "i18next-browser-languagedetector": "^7.2.0", "i18next-parser": "^8.13.0", "i18next-resources-to-backend": "^1.2.0", + "jest": "^29.7.0", "markdown-to-jsx": "^7.3.2", - "next": "^14.2.5", + "next": "^14.2.7", "next-i18n-router": "^4.1.1", "postcss": "8.4.36", "process": "^0.11.10", @@ -68,6 +73,7 @@ "react-hook-form": "^7.51.2", "react-i18next": "^14.0.5", "react-modal": "^3.16.1", + "react-number-format": "^5.4.1", "react-select": "^5.8.0", "react-toastify": "^10.0.5", "react-tooltip": "^5.26.3", @@ -86,6 +92,7 @@ "@babel/core": "^7.24.0", "@babel/eslint-parser": "^7.23.10", "@babel/runtime": "^7.23.1", + "@faker-js/faker": "^8.4.1", "@incubateur-ademe/nosgestesclimat-scripts": "^0.6.0", "@simonsmith/cypress-image-snapshot": "^9.0.1", "@types/mdx": "^2.0.10", @@ -118,6 +125,7 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "glob": "^10.4.1", + "jest-environment-jsdom": "^29.7.0", "json-stable-stringify": "^1.1.0", "postcss-loader": "^8.1.0", "prettier": "^3.1.0", diff --git a/public/images/misc/favicon.png b/public/favicon.png similarity index 100% rename from public/images/misc/favicon.png rename to public/favicon.png diff --git a/src/app/_fonts/Marianne-Bold.woff b/public/fonts/Marianne-Bold.woff similarity index 100% rename from src/app/_fonts/Marianne-Bold.woff rename to public/fonts/Marianne-Bold.woff diff --git a/src/app/_fonts/Marianne-Bold.woff2 b/public/fonts/Marianne-Bold.woff2 similarity index 100% rename from src/app/_fonts/Marianne-Bold.woff2 rename to public/fonts/Marianne-Bold.woff2 diff --git a/src/app/_fonts/Marianne-ExtraBold.woff b/public/fonts/Marianne-ExtraBold.woff similarity index 100% rename from src/app/_fonts/Marianne-ExtraBold.woff rename to public/fonts/Marianne-ExtraBold.woff diff --git a/src/app/_fonts/Marianne-ExtraBold.woff2 b/public/fonts/Marianne-ExtraBold.woff2 similarity index 100% rename from src/app/_fonts/Marianne-ExtraBold.woff2 rename to public/fonts/Marianne-ExtraBold.woff2 diff --git a/src/app/_fonts/Marianne-Light.woff b/public/fonts/Marianne-Light.woff similarity index 100% rename from src/app/_fonts/Marianne-Light.woff rename to public/fonts/Marianne-Light.woff diff --git a/src/app/_fonts/Marianne-Light.woff2 b/public/fonts/Marianne-Light.woff2 similarity index 100% rename from src/app/_fonts/Marianne-Light.woff2 rename to public/fonts/Marianne-Light.woff2 diff --git a/src/app/_fonts/Marianne-Medium.woff b/public/fonts/Marianne-Medium.woff similarity index 100% rename from src/app/_fonts/Marianne-Medium.woff rename to public/fonts/Marianne-Medium.woff diff --git a/src/app/_fonts/Marianne-Medium.woff2 b/public/fonts/Marianne-Medium.woff2 similarity index 100% rename from src/app/_fonts/Marianne-Medium.woff2 rename to public/fonts/Marianne-Medium.woff2 diff --git a/src/app/_fonts/Marianne-Regular.woff b/public/fonts/Marianne-Regular.woff similarity index 100% rename from src/app/_fonts/Marianne-Regular.woff rename to public/fonts/Marianne-Regular.woff diff --git a/src/app/_fonts/Marianne-Regular.woff2 b/public/fonts/Marianne-Regular.woff2 similarity index 100% rename from src/app/_fonts/Marianne-Regular.woff2 rename to public/fonts/Marianne-Regular.woff2 diff --git a/src/app/_fonts/Marianne-Thin.woff b/public/fonts/Marianne-Thin.woff similarity index 100% rename from src/app/_fonts/Marianne-Thin.woff rename to public/fonts/Marianne-Thin.woff diff --git a/src/app/_fonts/Marianne-Thin.woff2 b/public/fonts/Marianne-Thin.woff2 similarity index 100% rename from src/app/_fonts/Marianne-Thin.woff2 rename to public/fonts/Marianne-Thin.woff2 diff --git "a/public/images/blog/Ajouter des questions comple\314\201mentaires.gif" "b/public/images/blog/Ajouter des questions comple\314\201mentaires.gif" deleted file mode 100644 index 18e9545bd..000000000 Binary files "a/public/images/blog/Ajouter des questions comple\314\201mentaires.gif" and /dev/null differ diff --git a/public/images/icons/save-check.svg b/public/images/icons/save-check.svg new file mode 100644 index 000000000..2bbd6d313 --- /dev/null +++ b/public/images/icons/save-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/(layout-with-navigation)/[...not_found]/page.tsx b/src/app/(large-layout)/[...not_found]/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/[...not_found]/page.tsx rename to src/app/(large-layout)/[...not_found]/page.tsx diff --git a/src/app/(large-layout)/layout.tsx b/src/app/(large-layout)/layout.tsx new file mode 100644 index 000000000..1768540c5 --- /dev/null +++ b/src/app/(large-layout)/layout.tsx @@ -0,0 +1,12 @@ +import ContentLarge from '@/components/layout/ContentLarge' +import Header from '@/components/layout/Header' +import { PropsWithChildren } from 'react' + +export default async function LargeLayout({ children }: PropsWithChildren) { + return ( + <> +
+ {children} + + ) +} diff --git a/src/app/(layout-with-navigation)/northstar/_components/NorthStarIframe.tsx b/src/app/(large-layout)/northstar/_components/NorthStarIframe.tsx similarity index 88% rename from src/app/(layout-with-navigation)/northstar/_components/NorthStarIframe.tsx rename to src/app/(large-layout)/northstar/_components/NorthStarIframe.tsx index 4ee95d673..7cf6c1ed6 100644 --- a/src/app/(layout-with-navigation)/northstar/_components/NorthStarIframe.tsx +++ b/src/app/(large-layout)/northstar/_components/NorthStarIframe.tsx @@ -25,7 +25,7 @@ export default function NorthStarIframe() { ref={iFrameRef} id="iframe-metabase-northstar" title="Statistiques Northstar Metabase" - src="https://metabase-ngc.osc-fr1.scalingo.io/public/dashboard/0f6974c5-1254-47b4-b6d9-6e6f22a6faf7" + src="https://metabase.nosgestesclimat.fr/public/dashboard/0f6974c5-1254-47b4-b6d9-6e6f22a6faf7" width="100%" height="1800px" className="border-none"> diff --git a/src/app/(layout-with-navigation)/northstar/page.tsx b/src/app/(large-layout)/northstar/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/northstar/page.tsx rename to src/app/(large-layout)/northstar/page.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/StatsContent.tsx b/src/app/(large-layout)/stats/_components/StatsContent.tsx similarity index 93% rename from src/app/(layout-with-navigation)/stats/_components/StatsContent.tsx rename to src/app/(large-layout)/stats/_components/StatsContent.tsx index f30499da1..cd4267dc1 100644 --- a/src/app/(layout-with-navigation)/stats/_components/StatsContent.tsx +++ b/src/app/(large-layout)/stats/_components/StatsContent.tsx @@ -2,8 +2,6 @@ import Trans from '@/components/translation/Trans' import Title from '@/design-system/layout/Title' -import { useNumberSubscribers } from '@/hooks/useNumberSubscriber' -import { UseQueryResult } from '@tanstack/react-query' import { useAllSimulationsTerminees, useAllTimeVisits, @@ -13,7 +11,9 @@ import { useCurrentMonthVisits, useCurrentMonthWebsites, useGetSharedSimulationEvents, -} from '../_helpers/matomo' +} from '@/helpers/matomo' +import { useNumberSubscribers } from '@/hooks/useNumberSubscriber' +import { UseQueryResult } from '@tanstack/react-query' import AcquisitionBlock from './content/AcquisitionBlock' import MetabaseIframe from './content/MetabaseIframe' import SimulationsBlock from './content/SimulationsBlock' @@ -153,7 +153,7 @@ export default function StatsContent() { {' '} @@ -172,13 +172,13 @@ export default function StatsContent() {

Mode "Challenge tes amis"

{' '} diff --git a/src/app/(layout-with-navigation)/stats/_components/content/AcquisitionBlock.tsx b/src/app/(large-layout)/stats/_components/content/AcquisitionBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/AcquisitionBlock.tsx rename to src/app/(large-layout)/stats/_components/content/AcquisitionBlock.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/IframeFigures.js b/src/app/(large-layout)/stats/_components/content/IframeFigures.js similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/IframeFigures.js rename to src/app/(large-layout)/stats/_components/content/IframeFigures.js diff --git a/src/app/(layout-with-navigation)/stats/_components/content/MetabaseIframe.tsx b/src/app/(large-layout)/stats/_components/content/MetabaseIframe.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/MetabaseIframe.tsx rename to src/app/(large-layout)/stats/_components/content/MetabaseIframe.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/SimulationsBlock.tsx b/src/app/(large-layout)/stats/_components/content/SimulationsBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/SimulationsBlock.tsx rename to src/app/(large-layout)/stats/_components/content/SimulationsBlock.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/SimulationsChart.tsx b/src/app/(large-layout)/stats/_components/content/SimulationsChart.tsx similarity index 96% rename from src/app/(layout-with-navigation)/stats/_components/content/SimulationsChart.tsx rename to src/app/(large-layout)/stats/_components/content/SimulationsChart.tsx index f98c96b86..925875ad0 100644 --- a/src/app/(layout-with-navigation)/stats/_components/content/SimulationsChart.tsx +++ b/src/app/(large-layout)/stats/_components/content/SimulationsChart.tsx @@ -1,6 +1,5 @@ +import { useSimulationsChart } from '@/helpers/matomo' import { useEffect, useState } from 'react' - -import { useSimulationsChart } from '../../_helpers/matomo' import Chart from './chart/Chart' type Props = { diff --git a/src/app/(layout-with-navigation)/stats/_components/content/SimulationsFigures.tsx b/src/app/(large-layout)/stats/_components/content/SimulationsFigures.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/SimulationsFigures.tsx rename to src/app/(large-layout)/stats/_components/content/SimulationsFigures.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/Sources.js b/src/app/(large-layout)/stats/_components/content/Sources.js similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/Sources.js rename to src/app/(large-layout)/stats/_components/content/Sources.js diff --git a/src/app/(layout-with-navigation)/stats/_components/content/VisitsBlock.tsx b/src/app/(large-layout)/stats/_components/content/VisitsBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/VisitsBlock.tsx rename to src/app/(large-layout)/stats/_components/content/VisitsBlock.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/VisitsChart.tsx b/src/app/(large-layout)/stats/_components/content/VisitsChart.tsx similarity index 96% rename from src/app/(layout-with-navigation)/stats/_components/content/VisitsChart.tsx rename to src/app/(large-layout)/stats/_components/content/VisitsChart.tsx index a6172ab5d..6a6b7aca3 100644 --- a/src/app/(layout-with-navigation)/stats/_components/content/VisitsChart.tsx +++ b/src/app/(large-layout)/stats/_components/content/VisitsChart.tsx @@ -1,6 +1,5 @@ +import { useVisitsChart } from '@/helpers/matomo' import { useEffect, useState } from 'react' - -import { useVisitsChart } from '../../_helpers/matomo' import Chart from './chart/Chart' type Props = { diff --git a/src/app/(layout-with-navigation)/stats/_components/content/VisitsFigures.tsx b/src/app/(large-layout)/stats/_components/content/VisitsFigures.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/VisitsFigures.tsx rename to src/app/(large-layout)/stats/_components/content/VisitsFigures.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/chart/Chart.tsx b/src/app/(large-layout)/stats/_components/content/chart/Chart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/chart/Chart.tsx rename to src/app/(large-layout)/stats/_components/content/chart/Chart.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/chart/CustomTooltip.tsx b/src/app/(large-layout)/stats/_components/content/chart/CustomTooltip.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/chart/CustomTooltip.tsx rename to src/app/(large-layout)/stats/_components/content/chart/CustomTooltip.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/chart/Search.tsx b/src/app/(large-layout)/stats/_components/content/chart/Search.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/chart/Search.tsx rename to src/app/(large-layout)/stats/_components/content/chart/Search.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/content/sources/Table.js b/src/app/(large-layout)/stats/_components/content/sources/Table.js similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/content/sources/Table.js rename to src/app/(large-layout)/stats/_components/content/sources/Table.js diff --git a/src/app/(layout-with-navigation)/stats/_components/utils/FancySelect.tsx b/src/app/(large-layout)/stats/_components/utils/FancySelect.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/utils/FancySelect.tsx rename to src/app/(large-layout)/stats/_components/utils/FancySelect.tsx diff --git a/src/app/(layout-with-navigation)/stats/_components/utils/formatFigure.ts b/src/app/(large-layout)/stats/_components/utils/formatFigure.ts similarity index 100% rename from src/app/(layout-with-navigation)/stats/_components/utils/formatFigure.ts rename to src/app/(large-layout)/stats/_components/utils/formatFigure.ts diff --git a/src/app/(layout-with-navigation)/stats/page.tsx b/src/app/(large-layout)/stats/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/stats/page.tsx rename to src/app/(large-layout)/stats/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/filterRelevantMissingVariables.ts b/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/filterRelevantMissingVariables.ts deleted file mode 100644 index e56a303b7..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/filterRelevantMissingVariables.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DottedName } from '@/publicodes-state/types' - -// Gathered from nosgestesclimat-site -const filteredDottedNames: DottedName[] = [ - 'divers . publicité', - 'services sociétaux . pression locale', - 'services sociétaux . voter', - 'divers . aider les autres', - 'divers . partage NGC', - 'transport . infolettre', -] - -export const filterRelevantMissingVariables = ( - missingVariablesKeys: DottedName[], - extendedFoldedSteps: DottedName[] -) => { - return missingVariablesKeys.filter((dottedName: DottedName) => { - const isFolded = extendedFoldedSteps.indexOf(dottedName) >= 0 - const isManuallyExcluded = !filteredDottedNames?.includes(dottedName) - return isManuallyExcluded && !isFolded - }) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx b/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx deleted file mode 100644 index 712daac90..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import Trans from '@/components/translation/Trans' -import Badge from '@/design-system/layout/Badge' -import { JSX } from 'react' - -export default function ClassementMember({ - rank, - name, - quantity, - isTopThree, - isCurrentMember, -}: { - rank: JSX.Element | string - name: string - quantity: JSX.Element | string - isTopThree?: boolean - isCurrentMember?: boolean -}) { - return ( -
  • -
    - - {rank} - - - {name} - - {isCurrentMember && ( - - Vous - - )} -
    - -
    {quantity}
    -
  • - ) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/Simulateur.tsx b/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/Simulateur.tsx deleted file mode 100644 index a3cae4995..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/Simulateur.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client' - -import Total from '@/components/total/Total' -import { - simulateurCloseSommaire, - simulateurOpenSommaire, -} from '@/constants/tracking/pages/simulateur' -import { useDebug } from '@/hooks/useDebug' -import { trackEvent } from '@/utils/matomo/trackEvent' -import { useState } from 'react' -import Charts from './simulateur/Charts' -import Form from './simulateur/Form' -import Summary from './simulateur/Summary' - -export default function Simulateur() { - const isDebug = useDebug() - - const [isQuestionListOpen, setIsQuestionListOpen] = useState(false) - const toggleQuestionList = () => { - setIsQuestionListOpen((prevIsQuestionListOpen) => { - trackEvent( - prevIsQuestionListOpen - ? simulateurCloseSommaire - : simulateurOpenSommaire - ) - return !prevIsQuestionListOpen - }) - } - - return ( - <> - -
    -
    - -
    - - - ) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx b/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx deleted file mode 100644 index 71032ea3d..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useDebug } from '@/hooks/useDebug' -import { useForm } from '@/publicodes-state' -import Question from './summary/Question' - -type Props = { - toggleQuestionList: () => void - isQuestionListOpen: boolean -} -export default function Summary({ - toggleQuestionList, - isQuestionListOpen, -}: Props) { - const isDebug = useDebug() - - const { relevantQuestions } = useForm() - - return ( -
    - {relevantQuestions.map((question: any) => ( - - ))} -
    - ) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/form/ColorIndicator.tsx b/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/form/ColorIndicator.tsx deleted file mode 100644 index f51fed986..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/form/ColorIndicator.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { getBackgroundColor } from '@/helpers/getCategoryColorClass' -import { useRule } from '@/publicodes-state' - -type Props = { - question: string -} - -export default function ColorIndicator({ question }: Props) { - const { category } = useRule(question) - - return ( -
    - ) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/page.tsx b/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/page.tsx deleted file mode 100644 index bf4ae35b5..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client' - -import { useSimulateurGuard } from '@/hooks/navigation/useSimulateurGuard' -import { useTrackSimulateur } from '@/hooks/tracking/useTrackSimulateur' -import Faq from './_components/Faq' -import Simulateur from './_components/Simulateur' - -export default function SimulateurPage() { - // Guarding the route and redirecting if necessary - const { isGuardInit, isGuardRedirecting } = useSimulateurGuard() - - // We track the progression of the user in the simulation - useTrackSimulateur() - - if (!isGuardInit || isGuardRedirecting) return null - - return ( -
    - - - -
    - ) -} diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/page.tsx b/src/app/(layout-with-navigation)/(simulation)/tutoriel/page.tsx deleted file mode 100644 index 1a90bdc97..000000000 --- a/src/app/(layout-with-navigation)/(simulation)/tutoriel/page.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import Trans from '@/components/translation/Trans' -import Title from '@/design-system/layout/Title' -import ButtonStart from './_components/ButtonStart' - -import { noIndexObject } from '@/constants/metadata' -import { getServerTranslation } from '@/helpers/getServerTranslation' -import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import AutresQuestions from './_components/AutresQuestions' -import AvantDeCommencer from './_components/AvantDeCommencer' -import ButtonBack from './_components/ButtonBack' -import OrganisationMessage from './_components/OrganisationMessage' - -export async function generateMetadata() { - const { t } = await getServerTranslation() - - return getMetadataObject({ - title: t( - 'Calculer votre empreinte carbone individuelle - Nos Gestes Climat' - ), - description: t( - 'Comprenez comment calculer votre empreinte sur le climat en 10min chrono.' - ), - alternates: { - canonical: '/tutoriel', - }, - robots: noIndexObject, - }) -} - -export default async function Tutoriel() { - return ( -
    - - <span className="inline text-secondary-700"> - <Trans>10 minutes</Trans> - </span>{' '} - <Trans>chrono pour calculer votre empreinte carbone et eau</Trans> - </> - } - /> - - <AvantDeCommencer /> - - <div className="mb-8 flex justify-between border-b border-gray-200 pb-8"> - <ButtonBack /> - - <OrganisationMessage /> - - <ButtonStart /> - </div> - <AutresQuestions /> - </div> - ) -} diff --git a/src/app/(layout-with-navigation)/layout.tsx b/src/app/(layout-with-navigation)/layout.tsx deleted file mode 100644 index 195fabc63..000000000 --- a/src/app/(layout-with-navigation)/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Main from '@/design-system/layout/Main' -import { PropsWithChildren } from 'react' - -import Footer from '@/components/layout/Footer' -import { getServerTranslation } from '@/helpers/getServerTranslation' -import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' - -export async function generateMetadata() { - const { t } = await getServerTranslation() - - return getMetadataObject({ - title: t('Simulateur d’empreinte climat - Nos Gestes Climat'), - description: t( - 'Calculez votre empreinte sur le climat en 10 minutes chrono. Découvrez les gestes qui comptent vraiment pour le climat.' - ), - alternates: { - canonical: '/', - }, - }) -} - -export default async function PageLayout({ children }: PropsWithChildren) { - return ( - <> - <div className="m-auto flex max-w-7xl justify-start"> - <Main className="my-8 w-full max-w-5xl overflow-visible px-4 lg:mx-auto"> - {children} - </Main> - </div> - <Footer /> - </> - ) -} diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/a-propos/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/a-propos/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/a-propos/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/a-propos/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/accessibilite/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/accessibilite/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/accessibilite/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/accessibilite/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/budget/_components/RessourcesAllocationTable.tsx b/src/app/(narrow-layout)/(pages-statiques)/budget/_components/RessourcesAllocationTable.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/budget/_components/RessourcesAllocationTable.tsx rename to src/app/(narrow-layout)/(pages-statiques)/budget/_components/RessourcesAllocationTable.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/budget/_components/SelectYear.tsx b/src/app/(narrow-layout)/(pages-statiques)/budget/_components/SelectYear.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/budget/_components/SelectYear.tsx rename to src/app/(narrow-layout)/(pages-statiques)/budget/_components/SelectYear.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/budget/_data/budget.yaml b/src/app/(narrow-layout)/(pages-statiques)/budget/_data/budget.yaml similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/budget/_data/budget.yaml rename to src/app/(narrow-layout)/(pages-statiques)/budget/_data/budget.yaml diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/budget/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/budget/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/budget/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/budget/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/cgu/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/cgu/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/cgu/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/cgu/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/contact/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/contact/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/contact/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/contact/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/contextes-de-sondages/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/contextes-de-sondages/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/contextes-de-sondages/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/contextes-de-sondages/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/diffuser/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/diffuser/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/diffuser/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/diffuser/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/empreinte-climat/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/empreinte-climat/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/empreinte-climat/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/empreinte-climat/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/guide/[category]/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/guide/[category]/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/guide/[category]/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/guide/[category]/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/guide/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/guide/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/guide/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/guide/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/international/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/international/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/international/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/international/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/mentions-legales/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/mentions-legales/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/mentions-legales/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/mentions-legales/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx b/src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx similarity index 89% rename from src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx rename to src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx index 76102215c..ec1cf6189 100644 --- a/src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx +++ b/src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleDemoBlock.tsx @@ -4,15 +4,15 @@ import Link from '@/components/Link' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' import { useRules } from '@/hooks/useRules' import { safeEvaluateHelper } from '@/publicodes-state/helpers/safeEvaluateHelper' -import { DottedName, Rules, Situation } from '@/publicodes-state/types' +import { Situation } from '@/publicodes-state/types' import { encodeRuleName } from '@/utils/publicodes/encodeRuleName' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Engine, { Evaluation } from 'publicodes' import { useEffect, useMemo, useState } from 'react' -const demoDottedNames = [ +const demoDottedNames: DottedName[] = [ 'commun . intensité électricité', 'transport . voiture . thermique . empreinte au litre', - "logement . construction . durée d'amortissement", ] const indicatorsKeys = ['bilan', 'transport', 'logement'] @@ -25,10 +25,10 @@ export default function ModeleDemoBlock() { const { data: rules } = useRules({ isOptim: false }) - const engine = useMemo<Engine | null>( + const engine = useMemo( () => rules - ? new Engine(rules as Rules, { + ? new Engine<DottedName>(rules, { strict: { situation: false, noOrphanRule: false, @@ -65,7 +65,7 @@ export default function ModeleDemoBlock() { return obj }, - {} as { [key: (typeof demoDottedNames)[number]]: Evaluation } + {} as Record<(typeof demoDottedNames)[number], Evaluation> ) return ( @@ -85,7 +85,7 @@ export default function ModeleDemoBlock() { onChange(el, e.target.value === '' ? '' : e.target.value) } /> -  {(rules as Rules)?.[el]?.unité} +  {rules?.[el]?.unité} </span> </label> </li> diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleIssuePreviews.tsx b/src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleIssuePreviews.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleIssuePreviews.tsx rename to src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleIssuePreviews.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleStatsBlock.tsx b/src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleStatsBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/modele/_components/ModeleStatsBlock.tsx rename to src/app/(narrow-layout)/(pages-statiques)/modele/_components/ModeleStatsBlock.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/modele/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/modele/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/modele/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/modele/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/nos-relais/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/nos-relais/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/nos-relais/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/nos-relais/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/politique-de-confidentialite/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/politique-de-confidentialite/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/politique-de-confidentialite/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/politique-de-confidentialite/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/politique-des-cookies/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/politique-des-cookies/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/politique-des-cookies/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/politique-des-cookies/page.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/DoTheTest.tsx b/src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/DoTheTest.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/DoTheTest.tsx rename to src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/DoTheTest.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/FAQListItem.tsx b/src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/FAQListItem.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/FAQListItem.tsx rename to src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/FAQListItem.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/GithubContributionCard.tsx b/src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/GithubContributionCard.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/GithubContributionCard.tsx rename to src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/GithubContributionCard.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/Scroller.tsx b/src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/Scroller.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/_components/Scroller.tsx rename to src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/_components/Scroller.tsx diff --git a/src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/page.tsx b/src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(pages-statiques)/questions-frequentes/page.tsx rename to src/app/(narrow-layout)/(pages-statiques)/questions-frequentes/page.tsx diff --git a/src/app/(layout-with-navigation)/blog/[slug]/_components/ImpactCO2ScriptAdder.tsx b/src/app/(narrow-layout)/blog/[slug]/_components/ImpactCO2ScriptAdder.tsx similarity index 100% rename from src/app/(layout-with-navigation)/blog/[slug]/_components/ImpactCO2ScriptAdder.tsx rename to src/app/(narrow-layout)/blog/[slug]/_components/ImpactCO2ScriptAdder.tsx diff --git a/src/app/(layout-with-navigation)/blog/[slug]/layout.tsx b/src/app/(narrow-layout)/blog/[slug]/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/blog/[slug]/layout.tsx rename to src/app/(narrow-layout)/blog/[slug]/layout.tsx diff --git a/src/app/(layout-with-navigation)/blog/[slug]/page.tsx b/src/app/(narrow-layout)/blog/[slug]/page.tsx similarity index 87% rename from src/app/(layout-with-navigation)/blog/[slug]/page.tsx rename to src/app/(narrow-layout)/blog/[slug]/page.tsx index aca0ccf4e..fba3c9dd1 100644 --- a/src/app/(layout-with-navigation)/blog/[slug]/page.tsx +++ b/src/app/(narrow-layout)/blog/[slug]/page.tsx @@ -7,8 +7,8 @@ import Markdown from '@/design-system/utils/Markdown' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getPost } from '@/helpers/markdown/getPost' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { Post } from '@/types/posts' import { capitalizeString } from '@/utils/capitalizeString' +import { notFound } from 'next/navigation' type Props = { params: { slug: string } @@ -17,7 +17,7 @@ type Props = { export async function generateMetadata({ params: { slug } }: Props) { const { t } = await getServerTranslation() - const post = (await getPost('src/locales/blog/fr/', slug)) as Post + const post = await getPost('src/locales/blog/fr/', slug) return getMetadataObject({ title: `${capitalizeString(decodeURI(slug))?.replaceAll( @@ -26,7 +26,7 @@ export async function generateMetadata({ params: { slug } }: Props) { )}, ${t('article du blog - Nos Gestes Climat')}`, description: t('Découvrez les articles de blog du site Nos Gestes Climat.'), params: { slug }, - image: post.data.image, + image: post?.data?.image, alternates: { canonical: `/blog/${slug}`, }, @@ -34,7 +34,7 @@ export async function generateMetadata({ params: { slug } }: Props) { } export default async function BlogPost({ params: { slug } }: Props) { - const post = (await getPost('src/locales/blog/fr/', slug)) as Post + const post = await getPost('src/locales/blog/fr/', slug) const lastEditDate = await fetch( `https://api.github.com/repos/incubateur-ademe/nosgestesclimat-site-nextjs/commits?path=src%2Flocales%2Fblog%2Ffr%2F${slug}.mdx&page=1&per_page=1` @@ -44,16 +44,20 @@ export default async function BlogPost({ params: { slug } }: Props) { return json[0]?.commit?.committer?.date }) - const content = post.content - const data = post.data + if (!post) { + return notFound() + } + + const { content, data } = post return ( - <div> - <Link href="/blog" className="mb-6 block text-sm"> + <div className="m-auto max-w-2xl"> + <Link href="/blog" className="mb-8 block text-sm"> ← <Trans>Retour à la liste des articles</Trans> </Link> <PasserTestBanner /> + <Title title={data.title} /> {content ? ( diff --git a/src/app/(layout-with-navigation)/blog/page.tsx b/src/app/(narrow-layout)/blog/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/blog/page.tsx rename to src/app/(narrow-layout)/blog/page.tsx diff --git a/src/app/(narrow-layout)/layout.tsx b/src/app/(narrow-layout)/layout.tsx new file mode 100644 index 000000000..4c4b71959 --- /dev/null +++ b/src/app/(narrow-layout)/layout.tsx @@ -0,0 +1,12 @@ +import ContentNarrow from '@/components/layout/ContentNarrow' +import Header from '@/components/layout/Header' +import { PropsWithChildren } from 'react' + +export default async function NarrowLayout({ children }: PropsWithChildren) { + return ( + <> + <Header /> + <ContentNarrow>{children}</ContentNarrow> + </> + ) +} diff --git a/src/app/(layout-with-navigation)/nouveautes/[slug]/page.tsx b/src/app/(narrow-layout)/nouveautes/[slug]/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/nouveautes/[slug]/page.tsx rename to src/app/(narrow-layout)/nouveautes/[slug]/page.tsx diff --git a/src/app/(layout-with-navigation)/nouveautes/page.tsx b/src/app/(narrow-layout)/nouveautes/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/nouveautes/page.tsx rename to src/app/(narrow-layout)/nouveautes/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/_components/ActionDetail.tsx b/src/app/(simulation)/(large-layout)/actions/[...dottedName]/_components/ActionDetail.tsx similarity index 82% rename from src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/_components/ActionDetail.tsx rename to src/app/(simulation)/(large-layout)/actions/[...dottedName]/_components/ActionDetail.tsx index 1f98d74a7..2d7baf67f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/_components/ActionDetail.tsx +++ b/src/app/(simulation)/(large-layout)/actions/[...dottedName]/_components/ActionDetail.tsx @@ -5,6 +5,7 @@ import { actionsClickYes } from '@/constants/tracking/pages/actions' import ButtonLink from '@/design-system/inputs/ButtonLink' import Card from '@/design-system/layout/Card' import Markdown from '@/design-system/utils/Markdown' +import { filterRelevantMissingVariables } from '@/helpers/actions/filterRelevantMissingVariables' import { FormProvider, useCurrentSimulation, @@ -13,11 +14,10 @@ import { useTempEngine, useUser, } from '@/publicodes-state' -import { DottedName, NGCRuleNode } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { utils } from 'publicodes' import ActionForm from '../../_components/actions/_components/ActionForm' -import { filterRelevantMissingVariables } from '../../_helpers/filterRelevantMissingVariables' const { decodeRuleName, encodeRuleName } = utils @@ -33,18 +33,20 @@ export default function ActionDetail({ ?.map(decodeURIComponent) ?.join(' . ') - const { rules, getRuleObject, extendedFoldedSteps } = useTempEngine() + const { rules, getSpecialRuleObject, extendedFoldedSteps } = useTempEngine() const { toggleActionChoice } = useUser() const { actionChoices } = useCurrentSimulation() - const dottedName = decodeRuleName(formattedDottedName ?? '') + const dottedName = decodeRuleName(formattedDottedName ?? '') as DottedName - const remainingQuestions = filterRelevantMissingVariables( - Object.keys(getRuleObject(dottedName).missingVariables || {}), - extendedFoldedSteps - ) + const remainingQuestions = filterRelevantMissingVariables({ + missingVariables: Object.keys( + getSpecialRuleObject(dottedName).missingVariables || {} + ) as DottedName[], + extendedFoldedSteps, + }) const nbRemainingQuestions = remainingQuestions?.length const rule = useRule(dottedName) @@ -57,14 +59,15 @@ export default function ActionDetail({ const { description, icônes: icons } = rules[dottedName] - const flatActions = rules['actions'] + // Typing is shit here but it's the `actions` rule from model. + const flatActions = rules['actions'] as { formule: { somme: DottedName[] } } const relatedActions: NGCRuleNode[] = flatActions?.formule?.somme .filter( (action: DottedName) => action !== dottedName && getCategory(dottedName) === getCategory(action) ) - .map((name: string) => getRuleObject(name)) + .map((name: DottedName) => getSpecialRuleObject(name)) return ( <> diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/page.tsx b/src/app/(simulation)/(large-layout)/actions/[...dottedName]/page.tsx similarity index 95% rename from src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/page.tsx rename to src/app/(simulation)/(large-layout)/actions/[...dottedName]/page.tsx index 747778b8d..213b7ffe1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/[...dottedName]/page.tsx +++ b/src/app/(simulation)/(large-layout)/actions/[...dottedName]/page.tsx @@ -2,7 +2,7 @@ import Trans from '@/components/translation/Trans' import ButtonLink from '@/design-system/inputs/ButtonLink' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import ActionDetail from './_components/ActionDetail' export async function generateMetadata({ diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/ActionsChosenIndicator.tsx b/src/app/(simulation)/(large-layout)/actions/_components/ActionsChosenIndicator.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/ActionsChosenIndicator.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/ActionsChosenIndicator.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/ActionsTutorial.tsx b/src/app/(simulation)/(large-layout)/actions/_components/ActionsTutorial.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/ActionsTutorial.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/ActionsTutorial.tsx index 7112d31dd..f48a65854 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/ActionsTutorial.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/ActionsTutorial.tsx @@ -5,10 +5,10 @@ import { actionsClickStart } from '@/constants/tracking/pages/actions' import Button from '@/design-system/inputs/Button' import Card from '@/design-system/layout/Card' import Emoji from '@/design-system/utils/Emoji' +import { getCarbonFootprint } from '@/helpers/actions/getCarbonFootprint' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useEngine, useUser } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' -import { getCarbonFootprint } from '../_helpers/getCarbonFootprint' export default function ActionsTutorial() { const { t, i18n } = useClientTranslation() diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/AllerPlusLoin.tsx b/src/app/(simulation)/(large-layout)/actions/_components/AllerPlusLoin.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/AllerPlusLoin.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/AllerPlusLoin.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/OptionBar.tsx b/src/app/(simulation)/(large-layout)/actions/_components/OptionBar.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/OptionBar.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/OptionBar.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/SimulationMissing.tsx b/src/app/(simulation)/(large-layout)/actions/_components/SimulationMissing.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/SimulationMissing.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/SimulationMissing.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/Actions.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/Actions.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/Actions.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/Actions.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionCard.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionCard.tsx similarity index 85% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionCard.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionCard.tsx index e0892f8ca..e5b888574 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionCard.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionCard.tsx @@ -11,6 +11,8 @@ import { } from '@/constants/tracking/pages/actions' import NotificationBubble from '@/design-system/alerts/NotificationBubble' import Emoji from '@/design-system/utils/Emoji' +import { filterRelevantMissingVariables } from '@/helpers/actions/filterRelevantMissingVariables' +import { getIsActionDisabled } from '@/helpers/actions/getIsActionDisabled' import { getIsCustomAction } from '@/helpers/actions/getIsCustomAction' import { getBackgroundLightColor, @@ -24,12 +26,10 @@ import { useTempEngine, useUser, } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' import { encodeRuleName } from '@/utils/publicodes/encodeRuleName' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useCallback } from 'react' -import { filterRelevantMissingVariables } from '../../../_helpers/filterRelevantMissingVariables' -import { getIsActionDisabled } from '../../../_helpers/getIsActionDisabled' import ActionValue from './ActionValue' type Props = { @@ -57,11 +57,10 @@ export default function ActionCard({ const { dottedName, title, missingVariables, traversedVariables } = action const { icônes: icons } = rule || action - - const remainingQuestions = filterRelevantMissingVariables( - Object.keys(missingVariables || {}), - extendedFoldedSteps - ) + const remainingQuestions = filterRelevantMissingVariables({ + missingVariables: Object.keys(missingVariables || {}) as DottedName[], + extendedFoldedSteps, + }) const nbRemainingQuestions = remainingQuestions?.length @@ -85,7 +84,7 @@ export default function ActionCard({ return key === dottedName && actionChoices?.[key] }) - const flatRule = rules?.[dottedName] + const flatRule = (rules as any)?.[dottedName] const hasFormula = flatRule?.formule const isDisabled = @@ -165,22 +164,21 @@ export default function ActionCard({ /> {hasRemainingQuestions && ( - <NotificationBubble - onClick={() => setFocusedAction(dottedName)} - title={remainingQuestionsText} - number={nbRemainingQuestions} - /> - )} - - {hasRemainingQuestions && ( - <button - className="cursor-pointer text-sm text-primary-700" - onClick={() => { - trackEvent(actionsClickAdditionalQuestion(dottedName)) - setFocusedAction(dottedName) - }}> - {remainingQuestionsText} - </button> + <> + <NotificationBubble + onClick={() => setFocusedAction(dottedName)} + title={remainingQuestionsText} + number={nbRemainingQuestions} + /> + <button + className="cursor-pointer text-sm text-primary-700" + onClick={() => { + trackEvent(actionsClickAdditionalQuestion(dottedName)) + setFocusedAction(dottedName) + }}> + {remainingQuestionsText} + </button> + </> )} </div> <div className="self-bottom flex w-full justify-between px-2"> diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionForm.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionForm.tsx similarity index 86% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionForm.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionForm.tsx index b10db1360..8269b53b9 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionForm.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionForm.tsx @@ -3,11 +3,12 @@ import Navigation from '@/components/form/Navigation' import Question from '@/components/form/Question' import { useForm } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { motion } from 'framer-motion' import { useEffect } from 'react' type Props = { - category: string + category: DottedName onComplete: () => void } @@ -46,7 +47,11 @@ export default function ActionForm({ category, onComplete }: Props) { transition={{ duration: 0.3 }} className="mb-4 rounded-xl bg-primary-100 p-4 text-left"> <Question question={currentQuestion} key={currentQuestion} /> - <Navigation question={currentQuestion} onComplete={onComplete} /> + <Navigation + question={currentQuestion} + onComplete={onComplete} + isEmbedded + /> </motion.div> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionList.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionList.tsx similarity index 91% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionList.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionList.tsx index 13cc87ed6..9d6325dec 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionList.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionList.tsx @@ -8,8 +8,8 @@ import { useEngine, useUser, } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { Fragment } from 'react' import ActionCard from './ActionCard' import ActionForm from './ActionForm' import CustomActionForm from './actionList/CustomActionForm' @@ -19,7 +19,7 @@ type Props = { rules: any bilan: any focusedAction: string - setFocusedAction: (dottedName: DottedName) => void + setFocusedAction: (dottedName: string) => void } export default function ActionList({ @@ -50,13 +50,12 @@ export default function ActionList({ /> </li> ) - if (focusedAction === action.dottedName && !isFocusedActionCustom) { const convId = 'conv' return ( - <> - <li className="m-4 h-auto w-full" key={convId}> + <Fragment key={`${convId}-${focusedAction}`}> + <li className="m-4 h-auto w-full"> <FormProvider root={action.dottedName}> <ActionForm key={action.dottedName} @@ -86,13 +85,13 @@ export default function ActionList({ </li> {cardComponent} - </> + </Fragment> ) } if (isFocusedActionCustom && action.dottedName === focusedAction) { return ( - <> + <Fragment key={`custom-${focusedAction}`}> {cardComponent} <CustomActionForm @@ -100,7 +99,7 @@ export default function ActionList({ dottedName={action.dottedName} setFocusedAction={setFocusedAction} /> - </> + </Fragment> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionValue.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionValue.tsx similarity index 93% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionValue.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionValue.tsx index 134c1403f..fbbcedc0f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/ActionValue.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/ActionValue.tsx @@ -1,9 +1,9 @@ +import { getCarbonFootprint } from '@/helpers/actions/getCarbonFootprint' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' -import { DottedName, NodeValue } from '@/publicodes-state/types' import { TranslationFunctionType } from '@/types/translation' import { getCorrectedValue } from '@/utils/getCorrectedValue' -import { getCarbonFootprint } from '../../../_helpers/getCarbonFootprint' +import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat' const getFormattedActionValue = ( { t, i18n }: { t: TranslationFunctionType; i18n: any }, diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/actionList/CustomActionForm.tsx b/src/app/(simulation)/(large-layout)/actions/_components/actions/_components/actionList/CustomActionForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/actions/_components/actionList/CustomActionForm.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/actions/_components/actionList/CustomActionForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/CategoryFilters.tsx b/src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/CategoryFilters.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/CategoryFilters.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/CategoryFilters.tsx index 9e4d0a1c7..c91136840 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/CategoryFilters.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/CategoryFilters.tsx @@ -20,7 +20,7 @@ export default function CategoryFilters({ actions }: Props) { return ( <ul className="flex list-none flex-wrap justify-center gap-1 pl-0"> - {categories?.map((category: string) => { + {categories?.map((category) => { return ( <Filter key={category} diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/_components/Filter.tsx b/src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/_components/Filter.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/_components/Filter.tsx rename to src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/_components/Filter.tsx index a5602873b..6b7db8850 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_components/categoryFilters/_components/Filter.tsx +++ b/src/app/(simulation)/(large-layout)/actions/_components/categoryFilters/_components/Filter.tsx @@ -7,8 +7,8 @@ import { getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useRouter, useSearchParams } from 'next/navigation' type Props = { diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/layout.tsx b/src/app/(simulation)/(large-layout)/actions/layout.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/actions/layout.tsx rename to src/app/(simulation)/(large-layout)/actions/layout.tsx index 86f9f236d..513cbdd00 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/layout.tsx +++ b/src/app/(simulation)/(large-layout)/actions/layout.tsx @@ -28,7 +28,7 @@ export default function ActionsLayout({ children }: PropsWithChildren) { <FormProvider> <Title title={<Trans>Mes gestes</Trans>} /> - <Total /> + <Total simulationMode={false} /> <NorthStarBanner type="action" /> diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/page.tsx b/src/app/(simulation)/(large-layout)/actions/page.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/actions/page.tsx rename to src/app/(simulation)/(large-layout)/actions/page.tsx index 20467c49b..535112c7f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/page.tsx +++ b/src/app/(simulation)/(large-layout)/actions/page.tsx @@ -1,5 +1,6 @@ 'use client' +import getActions from '@/helpers/actions/getActions' import { useCurrentSimulation, useEngine, @@ -14,7 +15,6 @@ import OptionBar from './_components/OptionBar' import SimulationMissing from './_components/SimulationMissing' import Actions from './_components/actions/Actions' import CategoryFilters from './_components/categoryFilters/CategoryFilters' -import getActions from './_helpers/getActions' export default function ActionsPage({ searchParams, @@ -30,7 +30,7 @@ export default function ActionsPage({ const { actionChoices, progression } = useCurrentSimulation() - const { rules, getRuleObject } = useTempEngine() + const { rules, getSpecialRuleObject } = useTempEngine() const { safeEvaluate } = useSimulation() @@ -42,7 +42,7 @@ export default function ActionsPage({ rules, radical, safeEvaluate, - getRuleObject, + getSpecialRuleObject, actionChoices, }) diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/plus/[...dottedName]/page.tsx b/src/app/(simulation)/(large-layout)/actions/plus/[...dottedName]/page.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/actions/plus/[...dottedName]/page.tsx rename to src/app/(simulation)/(large-layout)/actions/plus/[...dottedName]/page.tsx index 3b28a181a..da33844cf 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/plus/[...dottedName]/page.tsx +++ b/src/app/(simulation)/(large-layout)/actions/plus/[...dottedName]/page.tsx @@ -5,7 +5,7 @@ import Markdown from '@/design-system/utils/Markdown' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getPost } from '@/helpers/markdown/getPost' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' export async function generateMetadata() { const { t } = await getServerTranslation() diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/plus/_components/ActionPlusList.tsx b/src/app/(simulation)/(large-layout)/actions/plus/_components/ActionPlusList.tsx similarity index 91% rename from src/app/(layout-with-navigation)/(simulation)/actions/plus/_components/ActionPlusList.tsx rename to src/app/(simulation)/(large-layout)/actions/plus/_components/ActionPlusList.tsx index d7fc4c5c4..a27992f61 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/plus/_components/ActionPlusList.tsx +++ b/src/app/(simulation)/(large-layout)/actions/plus/_components/ActionPlusList.tsx @@ -4,11 +4,14 @@ import Link from '@/components/Link' import Card from '@/design-system/layout/Card' import { getRuleTitle } from '@/helpers/publicodes/getRuleTitle' import { useTempEngine } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' -import { NGCRule, NGCRules } from '@incubateur-ademe/nosgestesclimat' import { Post } from '@/types/posts' import { encodeRuleName } from '@/utils/publicodes/encodeRuleName' +import { + DottedName, + NGCRule, + NGCRules, +} from '@incubateur-ademe/nosgestesclimat' type Props = { actions: Post[] diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/plus/page.tsx b/src/app/(simulation)/(large-layout)/actions/plus/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/plus/page.tsx rename to src/app/(simulation)/(large-layout)/actions/plus/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/creer/vos-informations/_component/GroupCreationForm.tsx b/src/app/(simulation)/(large-layout)/amis/creer/vos-informations/_component/GroupCreationForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/creer/vos-informations/_component/GroupCreationForm.tsx rename to src/app/(simulation)/(large-layout)/amis/creer/vos-informations/_component/GroupCreationForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/creer/vos-informations/page.tsx b/src/app/(simulation)/(large-layout)/amis/creer/vos-informations/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/creer/vos-informations/page.tsx rename to src/app/(simulation)/(large-layout)/amis/creer/vos-informations/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/creer/votre-groupe/_components/NameForm.tsx b/src/app/(simulation)/(large-layout)/amis/creer/votre-groupe/_components/NameForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/creer/votre-groupe/_components/NameForm.tsx rename to src/app/(simulation)/(large-layout)/amis/creer/votre-groupe/_components/NameForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/creer/votre-groupe/page.tsx b/src/app/(simulation)/(large-layout)/amis/creer/votre-groupe/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/creer/votre-groupe/page.tsx rename to src/app/(simulation)/(large-layout)/amis/creer/votre-groupe/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/_components/InvitationForm.tsx b/src/app/(simulation)/(large-layout)/amis/invitation/_components/InvitationForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/invitation/_components/InvitationForm.tsx rename to src/app/(simulation)/(large-layout)/amis/invitation/_components/InvitationForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/_components/LaconicRanking.tsx b/src/app/(simulation)/(large-layout)/amis/invitation/_components/LaconicRanking.tsx similarity index 87% rename from src/app/(layout-with-navigation)/(simulation)/amis/invitation/_components/LaconicRanking.tsx rename to src/app/(simulation)/(large-layout)/amis/invitation/_components/LaconicRanking.tsx index 1c6ae600a..02650055f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/_components/LaconicRanking.tsx +++ b/src/app/(simulation)/(large-layout)/amis/invitation/_components/LaconicRanking.tsx @@ -25,12 +25,15 @@ export default function LaconicRanking({ group }: Props) { } const particpantsOrdered = group.participants.sort((a, b) => { - if (!a.simulation.computedResults || !b.simulation.computedResults) { + const computedResultsA = a.simulation.computedResults + const computedResultsB = b.simulation.computedResults + + if (!computedResultsA || !computedResultsB) { return 0 } - return a.simulation.computedResults[defaultMetric].bilan < - b.simulation.computedResults[defaultMetric].bilan + return computedResultsA?.[defaultMetric]?.bilan < + computedResultsB?.[defaultMetric]?.bilan ? -1 : 1 }) diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/layout.tsx b/src/app/(simulation)/(large-layout)/amis/invitation/layout.tsx similarity index 88% rename from src/app/(layout-with-navigation)/(simulation)/amis/invitation/layout.tsx rename to src/app/(simulation)/(large-layout)/amis/invitation/layout.tsx index 0f75c7c50..5d72e00b7 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/layout.tsx +++ b/src/app/(simulation)/(large-layout)/amis/invitation/layout.tsx @@ -1,6 +1,5 @@ import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { FormProvider } from '@/publicodes-state' import { PropsWithChildren } from 'react' export async function generateMetadata() { @@ -20,5 +19,5 @@ export async function generateMetadata() { export default async function SimulateurLayout({ children, }: PropsWithChildren) { - return <FormProvider>{children}</FormProvider> + return <>{children}</> } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/invitation/page.tsx b/src/app/(simulation)/(large-layout)/amis/invitation/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/invitation/page.tsx rename to src/app/(simulation)/(large-layout)/amis/invitation/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/EditableGroupTitle.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/EditableGroupTitle.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/EditableGroupTitle.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/EditableGroupTitle.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/GroupResults.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/GroupResults.tsx similarity index 85% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/GroupResults.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/GroupResults.tsx index 695ef1226..0bc00c929 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/GroupResults.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/GroupResults.tsx @@ -9,28 +9,32 @@ import { useGetGroupStats } from '@/hooks/groups/useGetGroupStats' import { useIsGroupOwner } from '@/hooks/groups/useIsGroupOwner' import { useUser } from '@/publicodes-state' import { Group, Results } from '@/types/groups' +import { QueryObserverResult } from '@tanstack/react-query' import Classement from './groupResults/Classement' import InviteBlock from './groupResults/InviteBlock' import OwnerAdminSection from './groupResults/OwnerAdminSection' import ParticipantAdminSection from './groupResults/ParticipantAdminSection' import PointsFortsFaibles from './groupResults/PointsFortsFaibles' -type Props = { +export default function GroupResults({ + group, + refetchGroup, +}: { group: Group -} -export default function GroupResults({ group }: Props) { + refetchGroup: () => Promise<QueryObserverResult<Group, Error>> +}) { const { user } = useUser() const { isGroupOwner } = useIsGroupOwner({ group }) - const results: Results | null = useGetGroupStats({ + const results: Results = useGetGroupStats({ groupMembers: group.participants, userId: user.userId, }) return ( <> - <Classement group={group} /> + <Classement group={group} refetchGroup={refetchGroup} /> <InviteBlock group={group} /> diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/Classement.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/Classement.tsx similarity index 75% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/Classement.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/Classement.tsx index 21d9b7b8f..1fba63b56 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/Classement.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/Classement.tsx @@ -4,14 +4,22 @@ import { Group, Participant } from '@/types/groups' import { useState } from 'react' import Trans from '@/components/translation/Trans' -import { defaultMetric } from '@/constants/metric' import Emoji from '@/design-system/utils/Emoji' + +import { defaultMetric } from '@/constants/metric' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' import { getTopThreeAndRestMembers } from '@/helpers/groups/getTopThreeAndRestMembers' import { useUser } from '@/publicodes-state' +import { QueryObserverResult } from '@tanstack/react-query' import ClassementMember from './classement/ClassementMember' -export default function Classement({ group }: { group: Group }) { +export default function Classement({ + group, + refetchGroup, +}: { + group: Group + refetchGroup: () => Promise<QueryObserverResult<Group, Error>> +}) { const [isExpanded, setIsExpanded] = useState(false) const { @@ -55,12 +63,13 @@ export default function Classement({ group }: { group: Group }) { } const { formattedValue, unit } = formatCarbonFootprint( - participant?.simulation?.computedResults[defaultMetric].bilan ?? '' + participant?.simulation?.computedResults?.[defaultMetric]?.bilan ?? + '' ) - const quantity = participant?.simulation?.computedResults[ + const quantity = participant?.simulation?.computedResults?.[ defaultMetric - ].bilan ? ( + ]?.bilan ? ( <span className="m-none leading-[160%]"> <strong>{formattedValue}</strong>{' '} <span className="text-sm font-light">{unit}</span> @@ -77,29 +86,34 @@ export default function Classement({ group }: { group: Group }) { quantity={quantity} isTopThree isCurrentMember={participant.userId === userId} + group={group} + userId={participant.userId} + numberOfParticipants={group.participants.length} + refetchGroup={refetchGroup} /> ) })} </ul> {restOfMembers.length > 0 && ( - <ul className="px-4 py-4"> + <ul className="px-3 py-4"> {restOfMembers.length > 0 && restOfMembers .filter( - (member: Participant, index: number) => + (participant: Participant, index: number) => isExpanded || index + topThreeMembers?.length < 5 ) - .map((member: Participant, index: number) => { + .map((participant: Participant, index: number) => { const rank = `${index + 1 + topThreeMembers?.length}.` const { formattedValue, unit } = formatCarbonFootprint( - member?.simulation?.computedResults[defaultMetric].bilan ?? '' + participant?.simulation?.computedResults?.[defaultMetric] + ?.bilan ) - const quantity = member?.simulation?.computedResults[ + const quantity = participant?.simulation?.computedResults?.[ defaultMetric - ].bilan ? ( + ]?.bilan ? ( <span className="leading-[160%]"> <strong>{formattedValue}</strong>{' '} <span className="text-sm font-light">{unit}</span> @@ -110,11 +124,14 @@ export default function Classement({ group }: { group: Group }) { return ( <ClassementMember - key={member._id} - name={member.name} + key={participant._id} + name={participant.name} rank={rank} quantity={quantity} - isCurrentMember={member.userId === userId} + isCurrentMember={participant.userId === userId} + group={group} + userId={participant.userId} + refetchGroup={refetchGroup} /> ) })} diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/InviteBlock.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/InviteBlock.tsx similarity index 85% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/InviteBlock.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/InviteBlock.tsx index 29c2a2820..beba0f0b1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/InviteBlock.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/InviteBlock.tsx @@ -8,29 +8,22 @@ import { Group } from '@/types/groups' import { trackEvent } from '@/utils/matomo/trackEvent' import { useEffect, useRef, useState } from 'react' -type SubmitButtonProps = { - isShareDefined: boolean - handleShare: () => void - handleCopy: () => void - isCopied: boolean -} - const SubmitButton = ({ isShareDefined, handleShare, - handleCopy, isCopied, -}: SubmitButtonProps) => { +}: { + isShareDefined: boolean + handleShare: () => void + isCopied: boolean +}) => { return ( <Button className="flex justify-center whitespace-nowrap" onClick={() => { trackEvent(amisDashboardCopyLink) - if (isShareDefined) { - handleShare() - } else { - handleCopy() - } + + handleShare() }} data-cypress-id="invite-button"> {isShareDefined && <Trans>Partager</Trans>} @@ -53,19 +46,23 @@ export default function InviteBlock({ group }: { group: Group }) { } }, []) - const isShareDefined = - typeof navigator !== 'undefined' && navigator.share !== undefined + const shouldUseShareAPI = + typeof navigator !== 'undefined' && + navigator.share !== undefined && + window.innerWidth <= 768 const sharedURL = `${window.location.origin}/amis/invitation?groupId=${group?._id}&mtm_campaign=challenge-amis` const handleShare = async () => { - if (navigator.share) { + if (shouldUseShareAPI) { await navigator .share({ url: sharedURL, title: 'Rejoindre mon groupe', }) .catch(handleCopy) + } else { + handleCopy() } } @@ -84,9 +81,8 @@ export default function InviteBlock({ group }: { group: Group }) { <Trans>Invitez d'autres personnes à rejoindre votre groupe</Trans> </p> <SubmitButton - isShareDefined={isShareDefined} + isShareDefined={shouldUseShareAPI} isCopied={isCopied} - handleCopy={handleCopy} handleShare={handleShare} /> </div> @@ -106,9 +102,8 @@ export default function InviteBlock({ group }: { group: Group }) { </Trans> </p> <SubmitButton - isShareDefined={isShareDefined} + isShareDefined={shouldUseShareAPI} isCopied={isCopied} - handleCopy={handleCopy} handleShare={handleShare} /> </div> diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/OwnerAdminSection.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/OwnerAdminSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/OwnerAdminSection.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/OwnerAdminSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/ParticipantAdminSection.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/ParticipantAdminSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/ParticipantAdminSection.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/ParticipantAdminSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx similarity index 90% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx index f7c33c97d..4df9807c2 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/PointsFortsFaibles.tsx @@ -1,15 +1,15 @@ 'use client' import Trans from '@/components/translation/Trans' -import { Points } from '@/types/groups' +import type { PointsFortsFaiblesType } from '@/types/groups' import PointsListItem from './pointsFortsFaibles/PointsListItem' export default function PointsFortsFaibles({ pointsForts, pointsFaibles, }: { - pointsForts?: Points[] - pointsFaibles?: Points[] + pointsForts?: PointsFortsFaiblesType[] + pointsFaibles?: PointsFortsFaiblesType[] }) { return ( <div> diff --git a/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx new file mode 100644 index 000000000..fea5d4304 --- /dev/null +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/classement/ClassementMember.tsx @@ -0,0 +1,133 @@ +'use client' + +import TrashIcon from '@/components/icons/TrashIcon' +import Trans from '@/components/translation/Trans' +import Badge from '@/design-system/layout/Badge' +import ConfirmationModal from '@/design-system/modals/ConfirmationModal' +import { useIsGroupOwner } from '@/hooks/groups/useIsGroupOwner' +import { useRemoveParticipant } from '@/hooks/groups/useRemoveParticipant' +import { useClientTranslation } from '@/hooks/useClientTranslation' +import { Group } from '@/types/groups' +import { captureException } from '@sentry/nextjs' +import { QueryObserverResult } from '@tanstack/react-query' +import { JSX, useState } from 'react' +import { toast } from 'react-toastify' +import 'react-toastify/dist/ReactToastify.css' +import { twMerge } from 'tailwind-merge' + +export default function ClassementMember({ + rank, + quantity, + isTopThree, + isCurrentMember, + group, + name, + userId, + numberOfParticipants, + refetchGroup, +}: { + rank: JSX.Element | string + name: string + quantity: JSX.Element | string + isTopThree?: boolean + isCurrentMember?: boolean + group: Group + userId: string + numberOfParticipants?: number + refetchGroup: () => Promise<QueryObserverResult<Group, Error>> +}) { + const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false) + + const { t } = useClientTranslation() + + const { isGroupOwner } = useIsGroupOwner({ group }) + + const { mutateAsync: removePartipant } = useRemoveParticipant() + + async function handleDelete() { + if (!group) return + + try { + await removePartipant({ + groupId: group?._id, + userId: userId || '', + }) + + await refetchGroup() + + setIsConfirmationModalOpen(false) + + toast.success(t('Participant supprimé avec succès')) + } catch (error) { + toast.error(t('Une erreur est survenue'), { + autoClose: false, + }) + captureException(error) + } + } + + return ( + <li className="flex items-center justify-between gap-2"> + <div className="flex flex-1 items-center justify-between"> + <div className="mb-0 flex items-center text-sm md:text-base"> + <span + className={`mr-2 ${ + isTopThree ? 'text-lg md:text-2xl' : 'ml-1 text-base md:text-lg' + }`}> + {rank} + </span> + + {name} + + {isCurrentMember && ( + <Badge className="ml-2 inline rounded-xl border-pink-100 bg-pink-200 text-xs font-bold text-secondary-700"> + <Trans>Vous</Trans> + </Badge> + )} + </div> + + <div>{quantity}</div> + </div> + + {isGroupOwner && + (isCurrentMember ? ( + // Add a gap to keep the layout consistent + <div + className={ + numberOfParticipants === undefined || numberOfParticipants > 1 + ? 'w-6' + : '' + } + /> + ) : ( + <> + <button + onClick={() => setIsConfirmationModalOpen(true)} + className="inline-flex h-6 w-6 items-center justify-center !p-0 transition-colors" + aria-label={t('{{name}}, supprimer cette participation', { + name, + })}> + <TrashIcon + className={twMerge( + 'w-4 fill-default', + isTopThree ? 'fill-white hover:fill-primary-200' : '' + )} + /> + </button> + + {isConfirmationModalOpen && ( + <ConfirmationModal + onConfirm={handleDelete} + closeModal={() => setIsConfirmationModalOpen(false)}> + <p className="text-sm md:text-base"> + <Trans> + Supprimer ce participant ? Cette opération est définitive. + </Trans> + </p> + </ConfirmationModal> + )} + </> + ))} + </li> + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx similarity index 91% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx index ebf2fd09e..431aff084 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/pointsFortsFaibles/PointsListItem.tsx @@ -1,11 +1,12 @@ import Badge from '@/design-system/layout/Badge' import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { formatValue } from 'publicodes' import ValueDiff from './pointsListItem/ValueDiff' type PointsListItemProps = { - name: string + name: DottedName value: number difference: number } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/pointsFortsFaibles/pointsListItem/ValueDiff.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/pointsFortsFaibles/pointsListItem/ValueDiff.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/pointsFortsFaibles/pointsListItem/ValueDiff.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/pointsFortsFaibles/pointsListItem/ValueDiff.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx index 1995c8149..85813c7d1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/_components/groupResults/votreEmpreinte/Category.tsx @@ -1,11 +1,12 @@ import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' import { ValueObject } from '@/types/groups' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { formatValue } from 'publicodes' import ValueDiff from '../pointsFortsFaibles/pointsListItem/ValueDiff' type Props = { - category: string + category: DottedName categoryFootprint?: ValueObject membersLength: number } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/layout.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/layout.tsx similarity index 73% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/layout.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/layout.tsx index 47ab69212..ec5b56a24 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/layout.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/layout.tsx @@ -1,6 +1,5 @@ import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { FormProvider } from '@/publicodes-state' import { PropsWithChildren } from 'react' export async function generateMetadata() { @@ -17,9 +16,6 @@ export async function generateMetadata() { }) } -export default function Layout({ - params, - children, -}: PropsWithChildren<{ params: { root: string } }>) { - return <FormProvider root={params.root}>{children}</FormProvider> +export default function Layout({ children }: PropsWithChildren) { + return <>{children}</> } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/page.tsx b/src/app/(simulation)/(large-layout)/amis/resultats/page.tsx similarity index 81% rename from src/app/(layout-with-navigation)/(simulation)/amis/resultats/page.tsx rename to src/app/(simulation)/(large-layout)/amis/resultats/page.tsx index 5d8e38f91..c261700d1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/resultats/page.tsx +++ b/src/app/(simulation)/(large-layout)/amis/resultats/page.tsx @@ -6,6 +6,7 @@ import GoBackLink from '@/design-system/inputs/GoBackLink' import { useFetchGroup } from '@/hooks/groups/useFetchGroup' import { useGroupIdInQueryParams } from '@/hooks/groups/useGroupIdInQueryParams' import { useGroupPagesGuard } from '@/hooks/navigation/useGroupPagesGuard' +import { ToastContainer } from 'react-toastify' import EditableGroupTitle from './_components/EditableGroupTitle' import GroupResults from './_components/GroupResults' @@ -16,7 +17,11 @@ export default function GroupResultsPage() { }) const { groupIdInQueryParams } = useGroupIdInQueryParams() - const { data: group, isLoading } = useFetchGroup(groupIdInQueryParams) + const { + data: group, + isLoading, + refetch: refetchGroup, + } = useFetchGroup(groupIdInQueryParams) // If we are still fetching the group (or we are redirecting the user), we display a loader if (!isGuardInit || isGuardRedirecting || isLoading) { @@ -29,12 +34,14 @@ export default function GroupResultsPage() { } return ( - <div> + <div className="pb-8"> <GoBackLink href={'/classements'} className="mb-4 font-bold" /> <EditableGroupTitle group={group} /> - <GroupResults group={group} /> + <GroupResults group={group} refetchGroup={refetchGroup} /> + + <ToastContainer /> </div> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/supprimer/layout.tsx b/src/app/(simulation)/(large-layout)/amis/supprimer/layout.tsx similarity index 83% rename from src/app/(layout-with-navigation)/(simulation)/amis/supprimer/layout.tsx rename to src/app/(simulation)/(large-layout)/amis/supprimer/layout.tsx index 901ec0ba4..a5b295dc5 100644 --- a/src/app/(layout-with-navigation)/(simulation)/amis/supprimer/layout.tsx +++ b/src/app/(simulation)/(large-layout)/amis/supprimer/layout.tsx @@ -1,6 +1,5 @@ import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { FormProvider } from '@/publicodes-state' import { PropsWithChildren } from 'react' export async function generateMetadata() { @@ -18,8 +17,7 @@ export async function generateMetadata() { } export default function Layout({ - params, children, }: PropsWithChildren<{ params: { root: string } }>) { - return <FormProvider root={params.root}>{children}</FormProvider> + return <>{children}</> } diff --git a/src/app/(layout-with-navigation)/(simulation)/amis/supprimer/page.tsx b/src/app/(simulation)/(large-layout)/amis/supprimer/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/amis/supprimer/page.tsx rename to src/app/(simulation)/(large-layout)/amis/supprimer/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Groups.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Groups.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Groups.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Groups.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Organisations.tsx similarity index 93% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Organisations.tsx index 071426eb6..d62aa37c6 100644 --- a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations.tsx +++ b/src/app/(simulation)/(large-layout)/classements/_components/Organisations.tsx @@ -2,10 +2,10 @@ import Trans from '@/components/translation/Trans' import Title from '@/design-system/layout/Title' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useUser } from '@/publicodes-state' import Image from 'next/image' -import useFetchOrganisation from '../../organisations/_hooks/useFetchOrganisation' import CreateOrganisation from './Organisations/CreateOrganisation' import PollsList from './Organisations/PollsList' diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/CreateOrganisation.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/CreateOrganisation.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/CreateOrganisation.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Organisations/CreateOrganisation.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/PollsList.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/PollsList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/PollsList.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Organisations/PollsList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/OrganisationItem.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/OrganisationItem.tsx similarity index 95% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/OrganisationItem.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/OrganisationItem.tsx index 9ac6a16b3..7cd3f6552 100644 --- a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/OrganisationItem.tsx +++ b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/OrganisationItem.tsx @@ -1,9 +1,9 @@ 'use client' import Link from '@/components/Link' +import ChevronRight from '@/components/icons/ChevronRight' import Trans from '@/components/translation/Trans' import { classementClickOrganisation } from '@/constants/tracking/pages/classements' -import ChevronRight from '@/design-system/icons/ChevronRight' import Badge from '@/design-system/layout/Badge' import { Organisation } from '@/types/organisations' import { trackEvent } from '@/utils/matomo/trackEvent' diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/PollItem.tsx b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/PollItem.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/PollItem.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/PollItem.tsx index 028ef5591..a71aa5220 100644 --- a/src/app/(layout-with-navigation)/(simulation)/classements/_components/Organisations/pollList/PollItem.tsx +++ b/src/app/(simulation)/(large-layout)/classements/_components/Organisations/pollList/PollItem.tsx @@ -1,9 +1,9 @@ 'use client' import Link from '@/components/Link' +import ChevronRight from '@/components/icons/ChevronRight' import Trans from '@/components/translation/Trans' import { classementClickOrganisation } from '@/constants/tracking/pages/classements' -import ChevronRight from '@/design-system/icons/ChevronRight' import { getLinkToPollDashboard } from '@/helpers/navigation/pollPages' import { PollInfo } from '@/types/organisations' import { trackEvent } from '@/utils/matomo/trackEvent' diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/CreateFirstGroupSection.tsx b/src/app/(simulation)/(large-layout)/classements/_components/groups/CreateFirstGroupSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/CreateFirstGroupSection.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/groups/CreateFirstGroupSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/GroupContent.tsx b/src/app/(simulation)/(large-layout)/classements/_components/groups/GroupContent.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/GroupContent.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/groups/GroupContent.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/ServerErrorSection.tsx b/src/app/(simulation)/(large-layout)/classements/_components/groups/ServerErrorSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/ServerErrorSection.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/groups/ServerErrorSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/createOtherGroupsSection/GroupList.tsx b/src/app/(simulation)/(large-layout)/classements/_components/groups/createOtherGroupsSection/GroupList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/createOtherGroupsSection/GroupList.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/groups/createOtherGroupsSection/GroupList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx b/src/app/(simulation)/(large-layout)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx similarity index 97% rename from src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx rename to src/app/(simulation)/(large-layout)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx index e58c18f7c..7088b3b5f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx +++ b/src/app/(simulation)/(large-layout)/classements/_components/groups/createOtherGroupsSection/groupList/GroupItem.tsx @@ -1,9 +1,9 @@ 'use client' import Link from '@/components/Link' +import ChevronRight from '@/components/icons/ChevronRight' import Trans from '@/components/translation/Trans' import { classementClickGroup } from '@/constants/tracking/pages/classements' -import ChevronRight from '@/design-system/icons/ChevronRight' import Emoji from '@/design-system/utils/Emoji' import { getLinkToGroupDashboard } from '@/helpers/navigation/groupPages' import { useUser } from '@/publicodes-state' diff --git a/src/app/(layout-with-navigation)/(simulation)/classements/page.tsx b/src/app/(simulation)/(large-layout)/classements/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/classements/page.tsx rename to src/app/(simulation)/(large-layout)/classements/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/[question]/page.tsx b/src/app/(simulation)/(large-layout)/infos/[question]/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/[question]/page.tsx rename to src/app/(simulation)/(large-layout)/infos/[question]/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/_components/InfosProvider.tsx b/src/app/(simulation)/(large-layout)/infos/_components/InfosProvider.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/_components/InfosProvider.tsx rename to src/app/(simulation)/(large-layout)/infos/_components/InfosProvider.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/_components/Navigation.tsx b/src/app/(simulation)/(large-layout)/infos/_components/Navigation.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/_components/Navigation.tsx rename to src/app/(simulation)/(large-layout)/infos/_components/Navigation.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/codepostal/page.tsx b/src/app/(simulation)/(large-layout)/infos/codepostal/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/codepostal/page.tsx rename to src/app/(simulation)/(large-layout)/infos/codepostal/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/commencer/page.tsx b/src/app/(simulation)/(large-layout)/infos/commencer/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/commencer/page.tsx rename to src/app/(simulation)/(large-layout)/infos/commencer/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/email/page.tsx b/src/app/(simulation)/(large-layout)/infos/email/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/email/page.tsx rename to src/app/(simulation)/(large-layout)/infos/email/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/layout.tsx b/src/app/(simulation)/(large-layout)/infos/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/layout.tsx rename to src/app/(simulation)/(large-layout)/infos/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/infos/naissance/page.tsx b/src/app/(simulation)/(large-layout)/infos/naissance/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/infos/naissance/page.tsx rename to src/app/(simulation)/(large-layout)/infos/naissance/page.tsx diff --git a/src/app/(simulation)/(large-layout)/layout.tsx b/src/app/(simulation)/(large-layout)/layout.tsx new file mode 100644 index 000000000..1768540c5 --- /dev/null +++ b/src/app/(simulation)/(large-layout)/layout.tsx @@ -0,0 +1,12 @@ +import ContentLarge from '@/components/layout/ContentLarge' +import Header from '@/components/layout/Header' +import { PropsWithChildren } from 'react' + +export default async function LargeLayout({ children }: PropsWithChildren) { + return ( + <> + <Header /> + <ContentLarge>{children}</ContentLarge> + </> + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/CTACard.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/CTACard.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/CTACard.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/CTACard.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/MyPolls.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/MyPolls.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/MyPolls.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/MyPolls.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/NousContacter.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/NousContacter.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/NousContacter.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/NousContacter.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/OurTools.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/OurTools.tsx similarity index 98% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/OurTools.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/OurTools.tsx index 339ff0d15..c1b25586f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/OurTools.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/OurTools.tsx @@ -1,12 +1,12 @@ 'use client' +import ExternalLinkIcon from '@/components/icons/ExternalLinkIcon' import Trans from '@/components/translation/Trans' import { organisationsDashboardClickAteliers, organisationsDashboardClickImpactCo2, organisationsDashboardDownloadKit, } from '@/constants/tracking/pages/organisationsDashboard' -import ExternalLinkIcon from '@/design-system/icons/ExternalLinkIcon' import ButtonLink from '@/design-system/inputs/ButtonLink' import Title from '@/design-system/layout/Title' import { useClientTranslation } from '@/hooks/useClientTranslation' diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/QuestionsFrequentes.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/QuestionsFrequentes.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/QuestionsFrequentes.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/QuestionsFrequentes.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/myPolls/AddPollCard.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/myPolls/AddPollCard.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/myPolls/AddPollCard.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/myPolls/AddPollCard.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/myPolls/PollCard.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/myPolls/PollCard.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/_components/myPolls/PollCard.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/_components/myPolls/PollCard.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/AdminSection.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/AdminSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/AdminSection.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/AdminSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/FiltersProvider.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/FiltersProvider.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/FiltersProvider.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/FiltersProvider.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollNotFound.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollNotFound.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollNotFound.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollNotFound.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx similarity index 91% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx index f0963a14a..ca8010aca 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsCharts.tsx @@ -5,6 +5,7 @@ import Trans from '@/components/translation/Trans' import { carboneMetric } from '@/constants/metric' import Separator from '@/design-system/layout/Separator' import { SimulationRecap } from '@/types/organisations' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' import CategoryListItem from './pollStatisticsCharts/CategoryListItem' import MainFootprintChart from './pollStatisticsCharts/MainFootprintChart' @@ -19,8 +20,10 @@ export default function PollStatisticsCharts({ const maxValueOfAllCategories = useMemo( () => simulationRecaps?.reduce((acc, obj) => { - Object.keys( - obj.computedResults[carboneMetric].categories ?? {} + ;( + Object.keys( + obj.computedResults[carboneMetric].categories ?? {} + ) as DottedName[] ).forEach((category) => { const roundedValue = Math.round( obj.computedResults[carboneMetric].categories[category] / 1000 @@ -51,8 +54,10 @@ export default function PollStatisticsCharts({ // Calculate the mean for each category const meanCategories = useMemo( () => - Object.keys( - simulationRecaps?.[0]?.computedResults[carboneMetric].categories ?? {} + ( + Object.keys( + simulationRecaps?.[0]?.computedResults[carboneMetric].categories ?? {} + ) as DottedName[] ).map((category) => { const mean = simulationRecaps?.reduce( (acc, obj) => @@ -123,8 +128,10 @@ export default function PollStatisticsCharts({ </div> <ul> {simulationRecaps?.length > 0 && - Object.keys( - simulationRecaps[0].computedResults[carboneMetric].categories + ( + Object.keys( + simulationRecaps[0].computedResults[carboneMetric].categories + ) as DottedName[] ).map((category, index) => ( <CategoryListItem key={index} diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsFilters.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsFilters.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsFilters.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/PollStatisticsFilters.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx index 6e5c3f513..b153b6008 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/CategoryListItem.tsx @@ -5,13 +5,14 @@ import Emoji from '@/design-system/utils/Emoji' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' import { useRule } from '@/publicodes-state' import { SimulationRecap } from '@/types/organisations' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { t } from 'i18next' import RepartitionChart from './RepartitionChart' type Props = { simulationsRecap: SimulationRecap[] value: number - category: string + category: DottedName maxValue: number } diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/MainFootprintChart.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/MainFootprintChart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/MainFootprintChart.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/MainFootprintChart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/RepartitionChart.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/RepartitionChart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/RepartitionChart.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/RepartitionChart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/repartitionChart/BarItem.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/repartitionChart/BarItem.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/repartitionChart/BarItem.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsCharts/repartitionChart/BarItem.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/AgeFilter.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/AgeFilter.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/AgeFilter.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/AgeFilter.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/DepartementFilter.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/DepartementFilter.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/DepartementFilter.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/DepartementFilter.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/InfoTooltipIcon.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/InfoTooltipIcon.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/InfoTooltipIcon.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/_components/pollStatisticsFilters/InfoTooltipIcon.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/page.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/DeletePollButton.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/DeletePollButton.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/DeletePollButton.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/DeletePollButton.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/NameForm.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/NameForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/NameForm.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/_components/NameForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx similarity index 85% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx index c6acfad0c..bc7869d1b 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/campagnes/[pollSlug]/parametres/page.tsx @@ -1,17 +1,18 @@ 'use client' import MaxWidthContent from '@/components/layout/MaxWidthContent' -import ModificationSaved from '@/components/messages/ModificationSaved' +import ToastDisplay from '@/components/messages/ToastDisplay' import PollLoader from '@/components/organisations/PollLoader' import QuestionsComplementaires from '@/components/organisations/QuestionsComplementaires' import Trans from '@/components/translation/Trans' import Separator from '@/design-system/layout/Separator' import Title from '@/design-system/layout/Title' +import { displaySuccessToast } from '@/helpers/toasts/displaySuccessToast' import { useIsOrganisationAdmin } from '@/hooks/organisations/useIsOrganisationAdmin' import { usePoll } from '@/hooks/organisations/usePoll' import { useUpdateCustomQuestions } from '@/hooks/organisations/useUpdateCustomQuestions' import { useUpdatePoll } from '@/hooks/organisations/useUpdatePoll' -import { useAutoFlick } from '@/hooks/utils/useAutoFlick' +import { useClientTranslation } from '@/hooks/useClientTranslation' import { useUser } from '@/publicodes-state' import { useParams } from 'next/navigation' import { useEffect } from 'react' @@ -24,6 +25,8 @@ export default function ParametresPage() { const { user } = useUser() + const { t } = useClientTranslation() + const { isAdmin, isLoading: isLoadingOrgaAdmin } = useIsOrganisationAdmin() const { @@ -48,22 +51,22 @@ export default function ParametresPage() { orgaSlug: orgaSlug as string, }) - const { value, flick } = useAutoFlick() - // If the mutation status (of updatePoll or updatePollCustomQuestions) change to success, // we refetch the poll and display a confirmation message useEffect(() => { if (updatePollStatus === 'success') { - flick() + displaySuccessToast(t('Vos informations ont bien été mises à jour.')) + refetchPoll() } - }, [updatePollStatus, flick, refetchPoll]) + }, [updatePollStatus, refetchPoll, t]) useEffect(() => { if (updatePollCustomQuestionsStatus === 'success') { - flick() + displaySuccessToast(t('Vos informations ont bien été mises à jour.')) + refetchPoll() } - }, [updatePollCustomQuestionsStatus, flick, refetchPoll]) + }, [updatePollCustomQuestionsStatus, refetchPoll, t]) if (isLoading || isLoadingOrgaAdmin) { return <PollLoader /> @@ -104,11 +107,11 @@ export default function ParametresPage() { onChangeCustomQuestions={updatePollCustomQuestions} /> - <ModificationSaved shouldShowMessage={value} /> - <Separator className="my-4" /> <DeletePollButton /> + + <ToastDisplay /> </MaxWidthContent> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/creer-campagne/_components/PollForm.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/creer-campagne/_components/PollForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/creer-campagne/_components/PollForm.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/creer-campagne/_components/PollForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/creer-campagne/page.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/creer-campagne/page.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/creer-campagne/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/creer-campagne/page.tsx index 6cdace006..82e2cc33a 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/creer-campagne/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/creer-campagne/page.tsx @@ -3,10 +3,10 @@ import OrganisationFetchError from '@/components/organisations/OrganisationFetchError' import Trans from '@/components/translation/Trans' import Title from '@/design-system/layout/Title' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useUser } from '@/publicodes-state' import { useRouter } from 'next/navigation' import { useEffect } from 'react' -import useFetchOrganisation from '../../_hooks/useFetchOrganisation' import PollForm from './_components/PollForm' export default function CreerCampagnePage() { diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/page.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/page.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/page.tsx index 967d91979..7adb5dba7 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/page.tsx @@ -5,11 +5,11 @@ import OrganisationFetchError from '@/components/organisations/OrganisationFetch import Trans from '@/components/translation/Trans' import { organisationsDashboardClickParameters } from '@/constants/tracking/pages/organisationsDashboard' import ButtonLink from '@/design-system/inputs/ButtonLink' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useUser } from '@/publicodes-state' import { capitalizeString } from '@/utils/capitalizeString' import { useRouter } from 'next/navigation' import { useEffect } from 'react' -import useFetchOrganisation from '../_hooks/useFetchOrganisation' import MyPolls from './_components/MyPolls' import NousContacter from './_components/NousContacter' import OurTools from './_components/OurTools' diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/DeconnexionButton.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/DeconnexionButton.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/DeconnexionButton.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/DeconnexionButton.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/EmailVerificationModal.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/EmailVerificationModal.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/EmailVerificationModal.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/EmailVerificationModal.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx similarity index 63% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx index 0d7846fc4..02185a6a3 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/OrganisationFields.tsx @@ -4,20 +4,41 @@ import Trans from '@/components/translation/Trans' import { ORGANISATION_TYPES } from '@/constants/organisations/organisationTypes' import Select from '@/design-system/inputs/Select' import TextInputGroup from '@/design-system/inputs/TextInputGroup' +import { useClientTranslation } from '@/hooks/useClientTranslation' import { Organisation } from '@/types/organisations' -import { FieldValues, UseFormRegister } from 'react-hook-form' +import { FieldErrors, UseFormRegister } from 'react-hook-form' + +type Values = { + name: string + administratorName: string + hasOptedInForCommunications: boolean + organisationType: string + email: string + position: string + numberOfCollaborators: number + administratorTelephone: string +} type Props = { organisation: Organisation | undefined - register: UseFormRegister<FieldValues> + register: UseFormRegister<Values> + errors: FieldErrors } -export default function OrganisationFields({ organisation, register }: Props) { +export default function OrganisationFields({ + organisation, + register, + errors, +}: Props) { + const { t } = useClientTranslation() + + if (!organisation) return null + return ( <div className="flex flex-col gap-4"> <TextInputGroup label={<Trans>Votre organisation</Trans>} - value={organisation?.name} + value={organisation.name} {...register('name')} /> <Select @@ -30,7 +51,7 @@ export default function OrganisationFields({ organisation, register }: Props) { </span> </p> } - value={organisation?.organisationType} + value={organisation.organisationType} {...register('organisationType')}> {ORGANISATION_TYPES.map((type) => ( <option className="cursor-pointer" key={type} value={type}> @@ -49,8 +70,14 @@ export default function OrganisationFields({ organisation, register }: Props) { </span> </p> } - value={organisation?.numberOfCollaborators} - {...register('numberOfCollaborators')} + value={organisation.numberOfCollaborators} + {...register('numberOfCollaborators', { + min: { + value: 0, + message: t('Veuillez entrer un nombre positif.'), + }, + })} + error={(errors.numberOfCollaborators?.message as string) || ''} /> <TextInputGroup label={ @@ -62,7 +89,7 @@ export default function OrganisationFields({ organisation, register }: Props) { </span> </p> } - value={organisation?.administrators?.[0]?.position} + value={organisation.administrators?.[0]?.position} {...register('position')} />{' '} </div> diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx similarity index 86% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx index a72eb086e..039fb63d5 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/_components/PersonalInfoFields.tsx @@ -10,11 +10,13 @@ type Props = { } export default function PersonalInfoFields({ organisation, register }: Props) { + if (!organisation) return null + return ( <div className="flex flex-col gap-4"> <TextInputGroup label={<Trans>Votre prénom</Trans>} - value={organisation?.administrators?.[0]?.name} + value={organisation.administrators?.[0]?.name} {...register('administratorName')} /> @@ -28,13 +30,13 @@ export default function PersonalInfoFields({ organisation, register }: Props) { </span> </p> } - value={organisation?.administrators?.[0]?.telephone} + value={organisation.administrators?.[0]?.telephone} {...register('administratorTelephone')} /> <TextInputGroup label={<Trans>Votre e-mail</Trans>} - value={organisation?.administrators?.[0]?.email} + value={organisation.administrators?.[0]?.email} {...register('email')} /> @@ -42,7 +44,7 @@ export default function PersonalInfoFields({ organisation, register }: Props) { <CheckboxInputGroup size="xl" defaultChecked={ - organisation?.administrators?.[0]?.hasOptedInForCommunications + organisation.administrators?.[0]?.hasOptedInForCommunications } label={ <span> diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/layout.tsx similarity index 88% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/layout.tsx index a5d7657b5..17168d76c 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/layout.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/layout.tsx @@ -1,7 +1,7 @@ +import ToastDisplay from '@/components/messages/ToastDisplay' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' import { PropsWithChildren } from 'react' -import { ToastContainer } from 'react-toastify' export async function generateMetadata() { const { t } = await getServerTranslation() @@ -21,7 +21,7 @@ export default function Layout({ children }: PropsWithChildren) { return ( <> {children} - <ToastContainer /> + <ToastDisplay /> </> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/page.tsx b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/page.tsx similarity index 87% rename from src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/page.tsx index 322f88d8e..439bdf800 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/[orgaSlug]/parametres/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/[orgaSlug]/parametres/page.tsx @@ -9,6 +9,7 @@ import Form from '@/design-system/form/Form' import Separator from '@/design-system/layout/Separator' import Title from '@/design-system/layout/Title' import { displaySuccessToast } from '@/helpers/toasts/displaySuccessToast' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useSendVerificationCodeWhenModifyingEmail } from '@/hooks/organisations/useSendVerificationCodeWhenModifyingEmail' import { useUpdateOrganisation } from '@/hooks/organisations/useUpdateOrganisation' import { useVerifyCodeAndUpdate } from '@/hooks/organisations/useVerifyCodeAndUpdate' @@ -19,8 +20,6 @@ import { formatEmail } from '@/utils/format/formatEmail' import { trackEvent } from '@/utils/matomo/trackEvent' import { useEffect, useRef, useState } from 'react' import { SubmitHandler, useForm as useReactHookForm } from 'react-hook-form' -import 'react-toastify/dist/ReactToastify.css' -import useFetchOrganisation from '../../_hooks/useFetchOrganisation' import DeconnexionButton from './DeconnexionButton' import EmailVerificationModal from './_components/EmailVerificationModal' import OrganisationFields from './_components/OrganisationFields' @@ -47,19 +46,26 @@ export default function ParametresPage() { email: user?.organisation?.administratorEmail ?? '', }) - const { register, handleSubmit } = useReactHookForm({ - defaultValues: { - name: organisation?.name ?? '', - administratorName: organisation?.administrators?.[0]?.name ?? '', - hasOptedInForCommunications: - organisation?.administrators?.[0]?.hasOptedInForCommunications ?? false, - organisationType: organisation?.organisationType ?? '', - email: organisation?.administrators?.[0]?.email ?? '', - position: organisation?.administrators?.[0]?.position ?? '', - numberOfCollaborators: organisation?.numberOfCollaborators ?? 0, - administratorTelephone: - organisation?.administrators?.[0]?.telephone ?? '', - }, + const { + register, + handleSubmit, + formState: { errors }, + } = useReactHookForm({ + defaultValues: organisation + ? { + name: organisation?.name ?? '', + administratorName: organisation?.administrators?.[0]?.name ?? '', + hasOptedInForCommunications: + organisation?.administrators?.[0]?.hasOptedInForCommunications ?? + false, + organisationType: organisation?.organisationType ?? '', + email: organisation?.administrators?.[0]?.email ?? '', + position: organisation?.administrators?.[0]?.position ?? '', + numberOfCollaborators: organisation?.numberOfCollaborators ?? 0, + administratorTelephone: + organisation?.administrators?.[0]?.telephone ?? '', + } + : undefined, }) const { @@ -197,7 +203,8 @@ export default function ParametresPage() { <OrganisationFields organisation={organisation} - register={register as any} + register={register} + errors={errors} /> <Separator className="my-6" /> diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/CTAFooter.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/CTAFooter.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/CTAFooter.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/CTAFooter.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/HeroSection.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/HeroSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/HeroSection.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/HeroSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/IllustratedPointsList.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/IllustratedPointsList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/IllustratedPointsList.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/IllustratedPointsList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/PartnersSection.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/PartnersSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/PartnersSection.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/PartnersSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/VisuelIframe.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/VisuelIframe.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/VisuelIframe.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/VisuelIframe.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_components/heroSection/Illustration.tsx b/src/app/(simulation)/(large-layout)/organisations/_components/heroSection/Illustration.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_components/heroSection/Illustration.tsx rename to src/app/(simulation)/(large-layout)/organisations/_components/heroSection/Illustration.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/EmailSection.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/EmailSection.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/EmailSection.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/EmailSection.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/EmailForm.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/EmailForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/EmailForm.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/EmailForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/VerificationForm.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/VerificationForm.tsx similarity index 93% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/VerificationForm.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/VerificationForm.tsx index fa77943b0..019323048 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/VerificationForm.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/VerificationForm.tsx @@ -1,6 +1,6 @@ -import useTimeLeft from '@/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useTimeleft' -import useValidateVerificationCode from '@/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useValidateVerificationCode' import { useSendVerificationCode } from '@/hooks/organisations/useSendVerificationCode' +import useTimeLeft from '@/hooks/organisations/useTimeleft' +import useValidateVerificationCode from '@/hooks/organisations/useValidateVerificationCode' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useUser } from '@/publicodes-state' import dayjs from 'dayjs' diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/NotReceived.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/NotReceived.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/NotReceived.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/NotReceived.tsx diff --git a/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/VerificationCodeInput.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/VerificationCodeInput.tsx new file mode 100644 index 000000000..eb5897915 --- /dev/null +++ b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/VerificationCodeInput.tsx @@ -0,0 +1,67 @@ +import { marianne } from '@/app/layout' +import CheckCircleIcon from '@/components/icons/CheckCircleIcon' +import Trans from '@/components/translation/Trans' +import Loader from '@/design-system/layout/Loader' +import VerificationInput from 'react-verification-input' + +type Props = { + inputError: string | undefined + isSuccessValidate: boolean + isPendingValidate: boolean + handleValidateVerificationCode: (verificationCode: string) => void +} + +export default function VerificationCodeInput({ + inputError, + isSuccessValidate, + isPendingValidate, + handleValidateVerificationCode, +}: Props) { + return ( + <> + <VerificationInput + length={6} + classNames={{ + container: 'container w-[16rem] md:w-[20rem]', + character: `border-2 border-gray-300 rounded-xl w-[2rem] text-transparent font-medium ${ + marianne.className + } ${inputError ? '!border-red-700 border-2' : ''} ${ + isSuccessValidate ? '!border-green-700 border-2' : '' + }`, + characterInactive: 'text-transparent', + characterSelected: 'character--selected', + characterFilled: '!text-primary-700', + }} + onChange={handleValidateVerificationCode} + /> + + {inputError && ( + <div> + <p className="mt-2 text-sm text-red-700"> + <Trans>Le code est invalide</Trans> + </p> + </div> + )} + + {isPendingValidate && ( + <div className="mt-2 flex items-baseline gap-2 pl-2 text-xs"> + <Loader color="dark" size="sm" /> + + <span> + <Trans>Nous vérifions votre code...</Trans> + </span> + </div> + )} + + {isSuccessValidate && ( + <div className="mt-4 flex items-baseline gap-2 pl-2 text-sm"> + <CheckCircleIcon className="h-4 w-4 fill-green-700" /> + + <span className="text-green-700"> + <Trans>Votre code est valide !</Trans> + </span> + </div> + )} + </> + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/VerificationContent.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/VerificationContent.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/VerificationContent.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/VerificationContent.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/notReceived/ResendButton.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/notReceived/ResendButton.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/_components/emailSection/verificationForm/notReceived/ResendButton.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/_components/emailSection/verificationForm/notReceived/ResendButton.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/page.tsx b/src/app/(simulation)/(large-layout)/organisations/connexion/page.tsx similarity index 92% rename from src/app/(layout-with-navigation)/(simulation)/organisations/connexion/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/connexion/page.tsx index 5ad37a5fa..5161cc153 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/connexion/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/connexion/page.tsx @@ -2,10 +2,10 @@ import Trans from '@/components/translation/Trans' import Separator from '@/design-system/layout/Separator' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useUser } from '@/publicodes-state' import { useRouter } from 'next/navigation' import { useEffect } from 'react' -import useFetchOrganisation from '../_hooks/useFetchOrganisation' import EmailSection from './_components/EmailSection' export default function Page() { @@ -40,7 +40,7 @@ export default function Page() { return ( <section className="w-full bg-[#fff]"> - <div className="mx-auto max-w-5xl px-6 lg:px-0"> + <div className="max-w-5xl lg:px-0"> <h1> <Trans>Accédez à votre espace organisation</Trans> </h1> diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/creer/_components/CreationForm.tsx b/src/app/(simulation)/(large-layout)/organisations/creer/_components/CreationForm.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/creer/_components/CreationForm.tsx rename to src/app/(simulation)/(large-layout)/organisations/creer/_components/CreationForm.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/creer/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/creer/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/creer/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/creer/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/creer/page.tsx b/src/app/(simulation)/(large-layout)/organisations/creer/page.tsx similarity index 92% rename from src/app/(layout-with-navigation)/(simulation)/organisations/creer/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/creer/page.tsx index 0a5b1b055..8ea63eade 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/creer/page.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/creer/page.tsx @@ -3,8 +3,8 @@ import Trans from '@/components/translation/Trans' import Title from '@/design-system/layout/Title' import { useOrgaCreationGuard } from '@/hooks/navigation/useOrgaCreationGuard' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useUser } from '@/publicodes-state' -import useFetchOrganisation from '../_hooks/useFetchOrganisation' import CreationForm from './_components/CreationForm' export default function CreationPage() { diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/demander-demo/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/demander-demo/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/demander-demo/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/demander-demo/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/demander-demo/page.tsx b/src/app/(simulation)/(large-layout)/organisations/demander-demo/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/demander-demo/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/demander-demo/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/layout.tsx b/src/app/(simulation)/(large-layout)/organisations/layout.tsx similarity index 77% rename from src/app/(layout-with-navigation)/(simulation)/organisations/layout.tsx rename to src/app/(simulation)/(large-layout)/organisations/layout.tsx index 8755b6fad..582d9f16f 100644 --- a/src/app/(layout-with-navigation)/(simulation)/organisations/layout.tsx +++ b/src/app/(simulation)/(large-layout)/organisations/layout.tsx @@ -1,3 +1,4 @@ +import FilAriane from '@/components/layout/FilAriane' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' import { PropsWithChildren } from 'react' @@ -19,5 +20,10 @@ export async function generateMetadata() { } export default function Layout({ children }: PropsWithChildren) { - return <div className="bg-white">{children}</div> + return ( + <div className="bg-white md:-mt-8"> + <FilAriane className="-mt-4 mb-4" /> + {children} + </div> + ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/page.tsx b/src/app/(simulation)/(large-layout)/organisations/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/page.tsx rename to src/app/(simulation)/(large-layout)/organisations/page.tsx diff --git a/src/app/(layout-with-navigation)/personas/_components/Persona.tsx b/src/app/(simulation)/(large-layout)/personas/_components/Persona.tsx similarity index 87% rename from src/app/(layout-with-navigation)/personas/_components/Persona.tsx rename to src/app/(simulation)/(large-layout)/personas/_components/Persona.tsx index 0687c648e..3a3b50226 100644 --- a/src/app/(layout-with-navigation)/personas/_components/Persona.tsx +++ b/src/app/(simulation)/(large-layout)/personas/_components/Persona.tsx @@ -3,16 +3,18 @@ import Trans from '@/components/translation/Trans' import Button from '@/design-system/inputs/Button' import Card from '@/design-system/layout/Card' +import { fixSituationWithPartialMosaic } from '@/helpers/personas/fixSituationWithPartialMosaic' +import { getPersonaFoldedSteps } from '@/helpers/personas/getPersonaFoldedSteps' import { useDisposableEngine, useSimulation, useUser } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' -import { Persona as PersonaType } from '@incubateur-ademe/nosgestesclimat' +import { + DottedName, + Persona as PersonaType, +} from '@incubateur-ademe/nosgestesclimat' import { useRouter } from 'next/navigation' -import { fixSituationWithPartialMosaic } from '../_helpers/fixSituationWithPartialMosaic' -import { getPersonaFoldedSteps } from '../_helpers/getPersonaFoldedSteps' type Props = { persona: PersonaType - personaDottedName: DottedName + personaDottedName: string } export default function Persona({ persona, personaDottedName }: Props) { @@ -71,7 +73,7 @@ export default function Persona({ persona, personaDottedName }: Props) { engine, safeGetRule, safeEvaluate, - }), + }) as DottedName[], progression: 1, }) hideTutorial('testIntro') diff --git a/src/app/(layout-with-navigation)/personas/_components/PersonaExplanations.tsx b/src/app/(simulation)/(large-layout)/personas/_components/PersonaExplanations.tsx similarity index 100% rename from src/app/(layout-with-navigation)/personas/_components/PersonaExplanations.tsx rename to src/app/(simulation)/(large-layout)/personas/_components/PersonaExplanations.tsx diff --git a/src/app/(layout-with-navigation)/personas/_components/PersonaList.tsx b/src/app/(simulation)/(large-layout)/personas/_components/PersonaList.tsx similarity index 75% rename from src/app/(layout-with-navigation)/personas/_components/PersonaList.tsx rename to src/app/(simulation)/(large-layout)/personas/_components/PersonaList.tsx index 8b004f7ed..dcaae6597 100644 --- a/src/app/(layout-with-navigation)/personas/_components/PersonaList.tsx +++ b/src/app/(simulation)/(large-layout)/personas/_components/PersonaList.tsx @@ -1,10 +1,10 @@ 'use client' -import { Persona as PersonaType } from '@incubateur-ademe/nosgestesclimat' +import { Personas } from '@incubateur-ademe/nosgestesclimat' import Persona from './Persona' type Props = { - personas: Record<string, PersonaType> + personas: Personas } export default function PersonaList({ personas }: Props) { diff --git a/src/app/(layout-with-navigation)/personas/layout.tsx b/src/app/(simulation)/(large-layout)/personas/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/personas/layout.tsx rename to src/app/(simulation)/(large-layout)/personas/layout.tsx diff --git a/src/app/(layout-with-navigation)/personas/page.tsx b/src/app/(simulation)/(large-layout)/personas/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/personas/page.tsx rename to src/app/(simulation)/(large-layout)/personas/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/plan-du-site/_components/Actions.tsx b/src/app/(simulation)/(large-layout)/plan-du-site/_components/Actions.tsx similarity index 87% rename from src/app/(layout-with-navigation)/(simulation)/plan-du-site/_components/Actions.tsx rename to src/app/(simulation)/(large-layout)/plan-du-site/_components/Actions.tsx index be9acd28f..fd9b987b6 100644 --- a/src/app/(layout-with-navigation)/(simulation)/plan-du-site/_components/Actions.tsx +++ b/src/app/(simulation)/(large-layout)/plan-du-site/_components/Actions.tsx @@ -2,14 +2,14 @@ import Link from '@/components/Link' import Trans from '@/components/translation/Trans' +import getActions from '@/helpers/actions/getActions' import { useSimulation, useTempEngine } from '@/publicodes-state' import { utils } from 'publicodes' -import getActions from '../../actions/_helpers/getActions' export default function Actions() { const { safeEvaluate } = useSimulation() - const { rules, getRuleObject } = useTempEngine() + const { rules, getSpecialRuleObject } = useTempEngine() if (!rules) { return null @@ -19,7 +19,7 @@ export default function Actions() { rules, radical: true, safeEvaluate, - getRuleObject, + getSpecialRuleObject, actionChoices: [] as any[], }) diff --git a/src/app/(layout-with-navigation)/(simulation)/plan-du-site/_components/LinkList.tsx b/src/app/(simulation)/(large-layout)/plan-du-site/_components/LinkList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/plan-du-site/_components/LinkList.tsx rename to src/app/(simulation)/(large-layout)/plan-du-site/_components/LinkList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/plan-du-site/page.tsx b/src/app/(simulation)/(large-layout)/plan-du-site/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/plan-du-site/page.tsx rename to src/app/(simulation)/(large-layout)/plan-du-site/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/AnswerList.tsx b/src/app/(simulation)/(large-layout)/profil/_components/AnswerList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/AnswerList.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/AnswerList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/Localisation.tsx b/src/app/(simulation)/(large-layout)/profil/_components/Localisation.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/Localisation.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/Localisation.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/MesInformations.tsx b/src/app/(simulation)/(large-layout)/profil/_components/MesInformations.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/MesInformations.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/MesInformations.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/PersonaWarning.tsx b/src/app/(simulation)/(large-layout)/profil/_components/PersonaWarning.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/PersonaWarning.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/PersonaWarning.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/SimulationBanner.tsx b/src/app/(simulation)/(large-layout)/profil/_components/SimulationBanner.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/SimulationBanner.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/SimulationBanner.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/SimulationList.tsx b/src/app/(simulation)/(large-layout)/profil/_components/SimulationList.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/SimulationList.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/SimulationList.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/Category.tsx b/src/app/(simulation)/(large-layout)/profil/_components/answerList/Category.tsx similarity index 97% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/Category.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/answerList/Category.tsx index 45401b62e..bac803ff1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/Category.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/answerList/Category.tsx @@ -9,8 +9,8 @@ import { getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useRule, useSimulation } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useState } from 'react' import QuestionsWithoutSubcategory from './category/QuestionsWithoutSubcategory' import Subcategory from './category/Subcategory' diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx similarity index 95% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx index eb0678268..23e0a4fc9 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/QuestionsWithoutSubcategory.tsx @@ -2,7 +2,7 @@ import { getBackgroundColor } from '@/helpers/getCategoryColorClass' import { useForm, useSimulation } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Question from './subcategory/Question' type Props = { diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/Subcategory.tsx b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/Subcategory.tsx similarity index 97% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/Subcategory.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/answerList/category/Subcategory.tsx index 5ef07e1db..4b510502c 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/Subcategory.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/Subcategory.tsx @@ -8,8 +8,8 @@ import { getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useForm, useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useState } from 'react' import Question from './subcategory/Question' diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/Question.tsx b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/Question.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/Question.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/Question.tsx index 23e327e9d..0472dd4f0 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/Question.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/Question.tsx @@ -7,8 +7,8 @@ import { profilClickQuestion } from '@/constants/tracking/pages/profil' import { getLinkToSimulateur } from '@/helpers/navigation/simulateurPages' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import MosaicQuestion from './question/MosaicQuestion' type Props = { diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx index cbbb4d0e9..1b6d0f410 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/answerList/category/subcategory/question/MosaicQuestion.tsx @@ -4,7 +4,7 @@ import ChoicesValue from '@/components/misc/ChoicesValue' import NumberValue from '@/components/misc/NumberValue' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { question: DottedName diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/localisation/RegionModelAuthors.tsx b/src/app/(simulation)/(large-layout)/profil/_components/localisation/RegionModelAuthors.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/localisation/RegionModelAuthors.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/localisation/RegionModelAuthors.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/localisation/RegionSelector.tsx b/src/app/(simulation)/(large-layout)/profil/_components/localisation/RegionSelector.tsx similarity index 92% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/localisation/RegionSelector.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/localisation/RegionSelector.tsx index 306a2b0dd..a1bc3d253 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/_components/localisation/RegionSelector.tsx +++ b/src/app/(simulation)/(large-layout)/profil/_components/localisation/RegionSelector.tsx @@ -14,6 +14,8 @@ import Card from '@/design-system/layout/Card' import Loader from '@/design-system/layout/Loader' import Emoji from '@/design-system/utils/Emoji' import { sortSupportedRegions } from '@/helpers/localisation/sortSupportedRegions' +import { displaySuccessToast } from '@/helpers/toasts/displaySuccessToast' +import { useClientTranslation } from '@/hooks/useClientTranslation' import { useLocale } from '@/hooks/useLocale' import { useRules } from '@/hooks/useRules' import { useUser } from '@/publicodes-state' @@ -31,6 +33,8 @@ export default function RegionSelector({ }: Props) { const locale = useLocale() + const { t } = useClientTranslation() + const orderedSupportedRegions = sortSupportedRegions({ supportedRegions, currentLocale: locale, @@ -72,6 +76,9 @@ export default function RegionSelector({ code, name: supportedRegions[code][locale]?.nom as unknown as string, }) + + displaySuccessToast(t('Votre région a bien été mise à jour.')) + if (tutorials.localisationBanner) { showTutorial('localisationBanner') } diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/SimulationNotStarted.tsx b/src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/SimulationNotStarted.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/SimulationNotStarted.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/SimulationNotStarted.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/SimulationStarted.tsx b/src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/SimulationStarted.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/SimulationStarted.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/SimulationStarted.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/_components/TutorialLink.tsx b/src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/_components/TutorialLink.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/profil/_components/simulationBanner/_components/TutorialLink.tsx rename to src/app/(simulation)/(large-layout)/profil/_components/simulationBanner/_components/TutorialLink.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/profil/page.tsx b/src/app/(simulation)/(large-layout)/profil/page.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/profil/page.tsx rename to src/app/(simulation)/(large-layout)/profil/page.tsx index 2d8b15b51..4875fa3a0 100644 --- a/src/app/(layout-with-navigation)/(simulation)/profil/page.tsx +++ b/src/app/(simulation)/(large-layout)/profil/page.tsx @@ -1,3 +1,4 @@ +import ToastDisplay from '@/components/messages/ToastDisplay' import Trans from '@/components/translation/Trans' import UserInformationForm from '@/components/user/UserInformationForm' import Separator from '@/design-system/layout/Separator' @@ -72,6 +73,8 @@ export default async function Profil() { alt={t('Une personne livrant du courrier.')} /> </div> + + <ToastDisplay /> </FormProvider> ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/questions/_components/Questions.tsx b/src/app/(simulation)/(large-layout)/questions/_components/Questions.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/questions/_components/Questions.tsx rename to src/app/(simulation)/(large-layout)/questions/_components/Questions.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/questions/_components/questions/Question.tsx b/src/app/(simulation)/(large-layout)/questions/_components/questions/Question.tsx similarity index 64% rename from src/app/(layout-with-navigation)/(simulation)/questions/_components/questions/Question.tsx rename to src/app/(simulation)/(large-layout)/questions/_components/questions/Question.tsx index 82b3313a7..e5a957a5c 100644 --- a/src/app/(layout-with-navigation)/(simulation)/questions/_components/questions/Question.tsx +++ b/src/app/(simulation)/(large-layout)/questions/_components/questions/Question.tsx @@ -1,8 +1,9 @@ 'use client' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' -type Props = { question: string } +type Props = { question: DottedName } export default function Question({ question }: Props) { const { label } = useRule(question) diff --git a/src/app/(layout-with-navigation)/(simulation)/questions/page.tsx b/src/app/(simulation)/(large-layout)/questions/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/questions/page.tsx rename to src/app/(simulation)/(large-layout)/questions/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Choices.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/Choices.tsx similarity index 93% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/Choices.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/Choices.tsx index 3af546d56..53faf8cbc 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Choices.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/_components/Choices.tsx @@ -1,8 +1,8 @@ 'use client' import { quizClickAnswer } from '@/constants/tracking/pages/quiz' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' import Choice from './choices/Choice' diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Label.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/Label.tsx similarity index 98% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/Label.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/Label.tsx index 62d0f56b5..bc13032d2 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Label.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/_components/Label.tsx @@ -5,8 +5,8 @@ import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprin import { useEndPage } from '@/hooks/navigation/useEndPage' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { AnswerType } from '@/types/quiz' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { choices: DottedName[] diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Navigation.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/Navigation.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/Navigation.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/Navigation.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Results.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/Results.tsx similarity index 89% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/Results.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/Results.tsx index 15e908651..026cbf5bb 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/Results.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/_components/Results.tsx @@ -1,5 +1,5 @@ import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Category from './results/Category' type Props = { diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/choices/Choice.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/choices/Choice.tsx similarity index 92% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/choices/Choice.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/choices/Choice.tsx index be498dc23..ccf44a7c7 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/choices/Choice.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/_components/choices/Choice.tsx @@ -4,8 +4,8 @@ import ChoiceInput from '@/components/misc/ChoiceInput' import { quizClickAnswer } from '@/constants/tracking/pages/quiz' import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { answer: DottedName | null diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/results/Category.tsx b/src/app/(simulation)/(large-layout)/quiz/_components/results/Category.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/quiz/_components/results/Category.tsx rename to src/app/(simulation)/(large-layout)/quiz/_components/results/Category.tsx index d29f8ada1..fb35446e1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/_components/results/Category.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/_components/results/Category.tsx @@ -2,11 +2,12 @@ import HorizontalBarChartItem from '@/components/charts/HorizontalBarChartItem' import Trans from '@/components/translation/Trans' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' type Props = { index: number - choice: string + choice: DottedName isHeaviest: boolean maxValue: number } diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/layout.tsx b/src/app/(simulation)/(large-layout)/quiz/layout.tsx similarity index 88% rename from src/app/(layout-with-navigation)/(simulation)/quiz/layout.tsx rename to src/app/(simulation)/(large-layout)/quiz/layout.tsx index 3705dd034..54bf23aa6 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/layout.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/layout.tsx @@ -1,7 +1,6 @@ import { noIndexObject } from '@/constants/metadata' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { FormProvider } from '@/publicodes-state' import { PropsWithChildren } from 'react' export async function generateMetadata() { @@ -20,5 +19,5 @@ export async function generateMetadata() { } export default function Layout({ children }: PropsWithChildren) { - return <FormProvider>{children}</FormProvider> + return <>{children}</> } diff --git a/src/app/(layout-with-navigation)/(simulation)/quiz/page.tsx b/src/app/(simulation)/(large-layout)/quiz/page.tsx similarity index 97% rename from src/app/(layout-with-navigation)/(simulation)/quiz/page.tsx rename to src/app/(simulation)/(large-layout)/quiz/page.tsx index 7e6df9268..cb01503be 100644 --- a/src/app/(layout-with-navigation)/(simulation)/quiz/page.tsx +++ b/src/app/(simulation)/(large-layout)/quiz/page.tsx @@ -8,9 +8,9 @@ import { import { useQuizGuard } from '@/hooks/navigation/useQuizGuard' import { useSaveQuizAnswer } from '@/hooks/quiz/useSaveQuizAnswer' import { useSortedSubcategoriesByFootprint } from '@/hooks/useSortedSubcategoriesByFootprint' -import { DottedName } from '@/publicodes-state/types' import { AnswerType } from '@/types/quiz' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo, useState } from 'react' import Choices from './_components/Choices' import Label from './_components/Label' diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/Carbone.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/Carbone.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/Carbone.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/Carbone.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/DocumentationBlock.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/DocumentationBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/DocumentationBlock.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/DocumentationBlock.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/Eau.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/Eau.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/Eau.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/Eau.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/FeedbackBanner.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/FeedbackBanner.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/FeedbackBanner.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/FeedbackBanner.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/GetResultsByEmail.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/GetResultsByEmail.tsx similarity index 96% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/GetResultsByEmail.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/GetResultsByEmail.tsx index 5255af5f4..f0080201c 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/GetResultsByEmail.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/GetResultsByEmail.tsx @@ -126,7 +126,7 @@ export default function GetResultsByEmail({ } // We save the simulation (and signify the backend to send the email) - await saveSimulation({ + saveSimulation({ simulation: { ...currentSimulation, savedViaEmail: true, @@ -134,11 +134,15 @@ export default function GetResultsByEmail({ shouldSendSimulationEmail: true, listIds, }) - - // We update the simulation to signify that it has been saved (and not show the form anymore) - currentSimulation.update({ savedViaEmail: true }) } + useEffect(() => { + if (isSuccess && !currentSimulation.savedViaEmail) { + // We update the simulation to signify that it has been saved (and not show the form anymore) + currentSimulation.update({ savedViaEmail: true }) + } + }, [isSuccess, currentSimulation]) + // If we successfully saved the simulation, we display the confirmation message // or if the simulation is already saved if (isSuccess || currentSimulation?.savedViaEmail) { diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/InformationBlock.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/InformationBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/InformationBlock.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/InformationBlock.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/Poll.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/Poll.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/Poll.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/Poll.tsx index 1525c6812..795dde1bb 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/Poll.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/Poll.tsx @@ -50,7 +50,7 @@ export default function Poll() { pollSlug: lastPollSlug || '', })} trackingEvent={endClickPoll} - className="flex h-10 w-10 items-center justify-center rounded-full p-0 leading-none"> + className="flex !h-10 max-h-10 !w-10 max-w-10 items-center justify-center rounded-full p-0 leading-none"> → </ButtonLink> </Card> diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/ShareBlock.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/ShareBlock.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/ShareBlock.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/ShareBlock.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/OtherWays.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/OtherWays.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/OtherWays.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/OtherWays.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/Subcategories.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/Subcategories.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/Subcategories.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/Subcategories.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/getResultsByEmail/Confirmation.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/getResultsByEmail/Confirmation.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/getResultsByEmail/Confirmation.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/getResultsByEmail/Confirmation.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/Subcategory.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/Subcategory.tsx similarity index 90% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/Subcategory.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/Subcategory.tsx index 894f95d61..e64c41653 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/Subcategory.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/Subcategory.tsx @@ -1,11 +1,11 @@ import Trans from '@/components/translation/Trans' import Emoji from '@/design-system/utils/Emoji' import { - getBorderColor, + getBorderLightColor, getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' import Actions from './subcategory/Actions' @@ -25,7 +25,7 @@ export default function Subcategory({ subcategory, index }: Props) { className={twMerge( 'w-full', getTextDarkColor(category), - getBorderColor(category).replace('-categories', '') + '-100' + getBorderLightColor(category) )}> <div className="mb-4 flex items-start justify-between"> <div> diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/Actions.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/Actions.tsx similarity index 93% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/Actions.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/Actions.tsx index ca9fbca16..0fa61e8fb 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/Actions.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/Actions.tsx @@ -2,8 +2,8 @@ import Link from '@/components/Link' import Trans from '@/components/translation/Trans' import { endClickActions } from '@/constants/tracking/pages/end' import { useEngine, useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Action from './actions/Action' type Props = { @@ -23,7 +23,7 @@ export default function Actions({ subcategory, noNumberedFootprint }: Props) { const filteredActions = noNumberedFootprint ? actions - : actions?.filter((action: string) => getValue(action)) + : actions?.filter((action) => getValue(action)) if (!filteredActions?.length) return null @@ -35,7 +35,7 @@ export default function Actions({ subcategory, noNumberedFootprint }: Props) { return 1 }) : filteredActions - .map((action: string) => ({ + .map((action) => ({ dottedName: action, value: getValue(action) as number, })) diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx similarity index 94% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx index 0cc9b7fc9..3f2db3a84 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/Action.tsx @@ -7,7 +7,8 @@ import { getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useCurrentSimulation, useRule } from '@/publicodes-state' -import { DottedName, Metric } from '@/publicodes-state/types' +import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' const colorClassName = ['200', '100', '50'] diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx similarity index 95% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx index 8627efa4d..2d5950e85 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/carbone/subcategories/subcategory/actions/action/ActionButtons.tsx @@ -3,8 +3,8 @@ import CloseIcon from '@/components/icons/Close' import { endClickAction } from '@/constants/tracking/pages/end' import ButtonLink from '@/design-system/inputs/ButtonLink' import { useUser } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { action: DottedName diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/BlogArticles.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/BlogArticles.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/BlogArticles.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/BlogArticles.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/ClimateAndWater.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/ClimateAndWater.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/ClimateAndWater.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/ClimateAndWater.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/DomesticWater.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/DomesticWater.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/DomesticWater.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/DomesticWater.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/WaterActions.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/WaterActions.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/WaterActions.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/WaterActions.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/WaterDisclaimer.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/WaterDisclaimer.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/WaterDisclaimer.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/WaterDisclaimer.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/domesticWater/DomesticWaterContent.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/domesticWater/DomesticWaterContent.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/domesticWater/DomesticWaterContent.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/domesticWater/DomesticWaterContent.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/domesticWater/domesticWaterContent/DomesticWaterChart.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/domesticWater/domesticWaterContent/DomesticWaterChart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/eau/domesticWater/domesticWaterContent/DomesticWaterChart.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/eau/domesticWater/domesticWaterContent/DomesticWaterChart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/CarboneTargetContent.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/CarboneTargetContent.tsx similarity index 97% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/CarboneTargetContent.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/CarboneTargetContent.tsx index ff121033c..f2747e3a4 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/CarboneTargetContent.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/CarboneTargetContent.tsx @@ -1,6 +1,6 @@ import Link from '@/components/Link' +import ExternalLinkIcon from '@/components/icons/ExternalLinkIcon' import Trans from '@/components/translation/Trans' -import ExternalLinkIcon from '@/design-system/icons/ExternalLinkIcon' import Title from '@/design-system/layout/Title' import { useState } from 'react' import { twMerge } from 'tailwind-merge' diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/Hedgehog.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/Hedgehog.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/Hedgehog.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/Hedgehog.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/HedgehogAwareness.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/HedgehogAwareness.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/HedgehogAwareness.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/HedgehogAwareness.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/WaterFootprintContent.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/WaterFootprintContent.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/WaterFootprintContent.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/WaterFootprintContent.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/carboneTargetContent/AdditionalQuestions.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/carboneTargetContent/AdditionalQuestions.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/carboneTargetContent/AdditionalQuestions.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/carboneTargetContent/AdditionalQuestions.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/carboneTargetContent/TargetChart.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/carboneTargetContent/TargetChart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/fin/_components/informationBlock/carboneTargetContent/TargetChart.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/_components/informationBlock/carboneTargetContent/TargetChart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/layout.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/layout.tsx similarity index 87% rename from src/app/(layout-with-navigation)/(simulation)/fin/layout.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/layout.tsx index 62d6e57c7..d00265486 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/layout.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/layout.tsx @@ -1,7 +1,6 @@ import { noIndexObject } from '@/constants/metadata' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' -import { FormProvider } from '@/publicodes-state' import { PropsWithChildren } from 'react' export async function generateMetadata() { @@ -22,5 +21,5 @@ export async function generateMetadata() { } export default function Layout({ children }: PropsWithChildren) { - return <FormProvider>{children}</FormProvider> + return <>{children}</> } diff --git a/src/app/(layout-with-navigation)/(simulation)/fin/page.tsx b/src/app/(simulation)/(large-layout-nosticky)/fin/page.tsx similarity index 98% rename from src/app/(layout-with-navigation)/(simulation)/fin/page.tsx rename to src/app/(simulation)/(large-layout-nosticky)/fin/page.tsx index 2035bd312..8997b8772 100644 --- a/src/app/(layout-with-navigation)/(simulation)/fin/page.tsx +++ b/src/app/(simulation)/(large-layout-nosticky)/fin/page.tsx @@ -1,5 +1,6 @@ 'use client' +import MetricSlider from '@/components/fin/MetricSlider' import IframeDataShareModal from '@/components/iframe/IframeDataShareModal' import CategoriesAccordion from '@/components/results/CategoriesAccordion' import Trans from '@/components/translation/Trans' @@ -11,7 +12,6 @@ import { useCurrentMetric } from '@/hooks/useCurrentMetric' import { Metric } from '@/publicodes-state/types' import { ReactElement } from 'react' import { twMerge } from 'tailwind-merge' -import MetricSlider from '../../../../components/fin/MetricSlider' import Carbone from './_components/Carbone' import DocumentationBlock from './_components/DocumentationBlock' import Eau from './_components/Eau' diff --git a/src/app/(simulation)/(large-layout-nosticky)/layout.tsx b/src/app/(simulation)/(large-layout-nosticky)/layout.tsx new file mode 100644 index 000000000..008e406e3 --- /dev/null +++ b/src/app/(simulation)/(large-layout-nosticky)/layout.tsx @@ -0,0 +1,14 @@ +import ContentLarge from '@/components/layout/ContentLarge' +import Header from '@/components/layout/Header' +import { PropsWithChildren } from 'react' + +export default async function LargeLayoutNoSticky({ + children, +}: PropsWithChildren) { + return ( + <> + <Header isSticky={false} /> + <ContentLarge>{children}</ContentLarge> + </> + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/partage/layout.tsx b/src/app/(simulation)/(large-layout-nosticky)/partage/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/partage/layout.tsx rename to src/app/(simulation)/(large-layout-nosticky)/partage/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/partage/page.tsx b/src/app/(simulation)/(large-layout-nosticky)/partage/page.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/partage/page.tsx rename to src/app/(simulation)/(large-layout-nosticky)/partage/page.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/layout.tsx b/src/app/(simulation)/layout.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/layout.tsx rename to src/app/(simulation)/layout.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/Faq.tsx b/src/app/(simulation)/simulateur/[root]/_components/Faq.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/Faq.tsx rename to src/app/(simulation)/simulateur/[root]/_components/Faq.tsx diff --git a/src/app/(simulation)/simulateur/[root]/_components/SaveModal.tsx b/src/app/(simulation)/simulateur/[root]/_components/SaveModal.tsx new file mode 100644 index 000000000..166811dd5 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/SaveModal.tsx @@ -0,0 +1,180 @@ +'use client' + +import Trans from '@/components/translation/Trans' +import Button from '@/design-system/inputs/Button' +import Title from '@/design-system/layout/Title' +import Modal from '@/design-system/modals/Modal' +import { useSaveSimulation } from '@/hooks/simulation/useSaveSimulation' +import { useIframe } from '@/hooks/useIframe' +import { useCurrentSimulation, useUser } from '@/publicodes-state' +import { isEmailValid } from '@/utils/isEmailValid' +import { useRouter } from 'next/navigation' +import { useEffect, useState } from 'react' +import { SubmitHandler, useForm as useReactHookForm } from 'react-hook-form' +import SaveSimulationForm from './saveModal/SaveSimulationForm' + +type Props = { + isOpen: boolean + closeModal: () => void +} +type Inputs = { + email?: string +} + +export default function SaveModal({ isOpen, closeModal }: Props) { + const [isAlreadySavedSimulationUpdated, setIsAlreadySavedSimulationUpdated] = + useState(false) + + const currentSimulation = useCurrentSimulation() + + const { user, updateEmail } = useUser() + + const router = useRouter() + + const { register, handleSubmit } = useReactHookForm<Inputs>({ + defaultValues: { + email: user.email, + }, + }) + + const { saveSimulation, isPending, isSuccess, isError } = useSaveSimulation() + + const onSubmit: SubmitHandler<Inputs> = async (data) => { + // If the mutation is pending, we do nothing + if (isPending) { + return + } + + // Inputs validation + if (!data.email || !isEmailValid(data.email)) { + // setErrorEmail(t('Veuillez renseigner un email valide.')) + return + } + + updateEmail(data.email) + + // We save the simulation (and signify the backend to send the email) + saveSimulation({ + simulation: { + ...currentSimulation, + savedViaEmail: true, + }, + shouldSendSimulationEmail: true, + }) + } + + useEffect(() => { + if (isSuccess && !currentSimulation.savedViaEmail) { + // We update the simulation to signify that it has been saved (and not show the form anymore) + currentSimulation.update({ savedViaEmail: true }) + } + }, [isSuccess, currentSimulation]) + + // Handles the cases where the user has already saved the simulation in a useEffect + // by updating the simulation saved + useEffect(() => { + if ( + currentSimulation.savedViaEmail && + !isAlreadySavedSimulationUpdated && + !isSuccess && + !isError + ) { + saveSimulation({ + simulation: currentSimulation, + }) + setIsAlreadySavedSimulationUpdated(true) + } + }, [ + currentSimulation, + currentSimulation.savedViaEmail, + isAlreadySavedSimulationUpdated, + isError, + isSuccess, + saveSimulation, + ]) + + // We do not display the component if we are in an iframeSimulation context + const { isIframeOnlySimulation } = useIframe() + if (isIframeOnlySimulation) return null + + return ( + <Modal + isOpen={isOpen} + closeModal={closeModal} + hasAbortButton={false} + buttons={ + <> + <Button color="secondary" onClick={() => router.push('/')}> + {currentSimulation.savedViaEmail ? ( + <Trans>Revenir à l'accueil</Trans> + ) : ( + <Trans>Non, merci</Trans> + )} + </Button> + + {currentSimulation.savedViaEmail ? ( + <Button onClick={closeModal}>Continuer mon test</Button> + ) : ( + <Button + type="submit" + form={'save-form'} + disabled={isPending} + className="inline"> + <Trans> + Sauvegarder{' '} + <span className="hidden lg:inline">ma progression</span> + </Trans> + </Button> + )} + </> + }> + {currentSimulation.savedViaEmail && ( + <Title + tag="h2" + hasSeparator={false} + className="flex items-center gap-1" + subtitle={ + <Trans> + Vous pouvez le reprendre plus tard en cliquant sur le lien que + vous avez reçu par email. + </Trans> + }> + <Trans>Votre test est sauvegardé !</Trans> + <svg + className="inline-block h-8 w-8" + viewBox="0 0 100 100" + xmlns="http://www.w3.org/2000/svg"> + <path + fill="none" + stroke="rgb(22, 163, 74)" + strokeWidth="8" + strokeLinecap="round" + strokeLinejoin="round" + strokeDasharray="200" + strokeDashoffset="200" + d="M20 50 L40 70 L80 30"> + <animate + attributeName="stroke-dashoffset" + from="200" + to="0" + dur="1s" + begin="0s" + fill="freeze" + calcMode="linear" + /> + </path> + </svg> + + )} + + {!currentSimulation.savedViaEmail && ( + + )} + + ) +} diff --git a/src/app/(simulation)/simulateur/[root]/_components/Simulateur.tsx b/src/app/(simulation)/simulateur/[root]/_components/Simulateur.tsx new file mode 100644 index 000000000..8d89f2cf4 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/Simulateur.tsx @@ -0,0 +1,35 @@ +'use client' + +import { useDebug } from '@/hooks/useDebug' +import { twMerge } from 'tailwind-merge' +import Form from './simulateur/Form' +import Summary from './simulateur/Summary' + +type Props = { + toggleQuestionList: () => void + isQuestionListOpen: boolean +} +export default function Simulateur({ + toggleQuestionList, + isQuestionListOpen, +}: Props) { + const isDebug = useDebug() + + return ( +
    + {isQuestionListOpen && ( + + )} +
    + +
    +
    + ) +} diff --git a/src/app/(simulation)/simulateur/[root]/_components/saveModal/SaveSimulationForm.tsx b/src/app/(simulation)/simulateur/[root]/_components/saveModal/SaveSimulationForm.tsx new file mode 100644 index 000000000..23e350269 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/saveModal/SaveSimulationForm.tsx @@ -0,0 +1,56 @@ +import Trans from '@/components/translation/Trans' +import TextInputGroup from '@/design-system/inputs/TextInputGroup' +import Title from '@/design-system/layout/Title' +import { + SubmitHandler, + UseFormHandleSubmit, + UseFormRegister, +} from 'react-hook-form' + +type Inputs = { + email?: string +} + +export default function SaveSimulationForm({ + handleSubmit, + onSubmit, + register, + isError, +}: { + handleSubmit: UseFormHandleSubmit + onSubmit: SubmitHandler + register: UseFormRegister + isError: boolean +}) { + return ( + + + Recevez par email un lien pour reprendre votre test plus tard. + </Trans> + }> + <Trans>Reprendre plus tard</Trans> + + +
    + + + {isError && ( +

    + Une erreur s'est produite au moment de la sauvegarde. +

    + )} +
    + + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Charts.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/Charts.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Charts.tsx rename to src/app/(simulation)/simulateur/[root]/_components/simulateur/Charts.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx similarity index 74% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx rename to src/app/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx index c9cde68ad..374ba1bd8 100644 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/Form.tsx @@ -1,8 +1,12 @@ +'use client' + import { PreventNavigationContext } from '@/app/_components/mainLayoutProviders/PreventNavigationProvider' import Navigation from '@/components/form/Navigation' import Question from '@/components/form/Question' +import ContentLarge from '@/components/layout/ContentLarge' import questions from '@/components/specialQuestions' import { simulationSimulationCompleted } from '@/constants/tracking/simulation' +import { getBgCategoryColor } from '@/helpers/getCategoryColorClass' import { uuidToNumber } from '@/helpers/uuidToNumber' import { useEndPage } from '@/hooks/navigation/useEndPage' import { useTrackTimeOnSimulation } from '@/hooks/tracking/useTrackTimeOnSimulation' @@ -11,7 +15,9 @@ import { useQuestionInQueryParams } from '@/hooks/useQuestionInQueryParams' import { useCurrentSimulation, useEngine, useForm } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' import { useContext, useEffect, useState } from 'react' -import ColorIndicator from './form/ColorIndicator' +import CategoriesSummary from './form/CategoriesSummary' +import FunFact from './form/FunFact' +import CategoryIllustration from './summary/CategoryIllustration' export default function Form() { const isDebug = useDebug() @@ -23,6 +29,7 @@ export default function Form() { relevantAnsweredQuestions, currentQuestion, setCurrentQuestion, + currentCategory, } = useForm() const { questionInQueryParams, setQuestionInQueryParams } = @@ -108,14 +115,31 @@ export default function Form() { const QuestionComponent = questions[currentQuestion] || Question return ( -
    - - + <> + +
    +
    + +
    + +
    + + + + +
    + +
    +
    +
    +
    + -
    + ) } diff --git a/src/app/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx new file mode 100644 index 000000000..044bbef50 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/Summary.tsx @@ -0,0 +1,38 @@ +'use client' + +import ContentLarge from '@/components/layout/ContentLarge' +import Title from '@/design-system/layout/Title' +import { useDebug } from '@/hooks/useDebug' +import { useForm } from '@/publicodes-state' +import Question from './summary/Question' + +type Props = { + toggleQuestionList: () => void + isQuestionListOpen: boolean +} +export default function Summary({ + toggleQuestionList, + isQuestionListOpen, +}: Props) { + const isDebug = useDebug() + + const { relevantQuestions } = useForm() + + return ( +
    + + + Toutes les questions + + {relevantQuestions.map((question: any, index: number) => ( + + ))} + +
    + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/charts/InlineChart.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/charts/InlineChart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/charts/InlineChart.tsx rename to src/app/(simulation)/simulateur/[root]/_components/simulateur/charts/InlineChart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx similarity index 92% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx rename to src/app/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx index c856d5fc7..19d6dc19c 100644 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/charts/inlineChart/CategoriesChart.tsx @@ -12,7 +12,7 @@ export default function CategoriesChart() { return ( <>
    - {categories.map((category: string, index: number) => ( + {categories.map((category, index: number) => ( (currentCategory && - subcategories[currentCategory]?.filter((subcategory: string) => + subcategories[currentCategory]?.filter((subcategory) => checkIfValid(subcategory) )) || [], @@ -37,7 +40,7 @@ export default function SubcategoriesChart() { className={`mb-4 flex h-8 rounded-md md:h-12 md:rounded-xl ${getBackgroundColor( currentCategory )}`}> - {filteredSubcategories.map((subcategory: string, index: number) => ( + {filteredSubcategories.map((subcategory, index: number) => ( + {categories.map((category) => ( + + ))} +
    + ) +} diff --git a/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/FunFact.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/FunFact.tsx new file mode 100644 index 000000000..6a7107925 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/FunFact.tsx @@ -0,0 +1,39 @@ +'use client' + +import Trans from '@/components/translation/Trans' +import Emoji from '@/design-system/utils/Emoji' +import { + getBgCategoryColor, + getBorderCategoryColor, +} from '@/helpers/getCategoryColorClass' +import { useForm } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' + +type Props = { + question: DottedName +} + +export default function FunFact({ question }: Props) { + const { currentCategory } = useForm() + + if (question !== 'logement . âge') return null + + return ( +
    +

    + 💡 Le saviez vous ? +

    +

    + + + La taille des logements français a très fortement augmenté + {' '} + sur ces 50 dernirèes années, passant de 23 à 40,4 m² par habitant, + soit{' '} + 90,9 m² en moyenne pour un foyer de 2,2 personnes. + +

    +
    + ) +} diff --git a/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/categoriesSummary/Category.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/categoriesSummary/Category.tsx new file mode 100644 index 000000000..99036444b --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/form/categoriesSummary/Category.tsx @@ -0,0 +1,87 @@ +import Emoji from '@/design-system/utils/Emoji' +import { formatFootprint } from '@/helpers/formatters/formatFootprint' +import { + getBackgroundLightColor, + getBorderColor, + getBorderLightColor, + getTextDarkColor, +} from '@/helpers/getCategoryColorClass' +import { useClientTranslation } from '@/hooks/useClientTranslation' +import { useLocale } from '@/hooks/useLocale' +import { useForm, useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { twMerge } from 'tailwind-merge' + +type Props = { + category: DottedName +} + +export default function Category({ category }: Props) { + const locale = useLocale() + const { t } = useClientTranslation() + + const { + remainingQuestionsByCategories, + currentCategory, + questionsByCategories, + } = useForm() + + const { title, icons, numericValue } = useRule(category) + + const completion = + (questionsByCategories[category].length - + remainingQuestionsByCategories[category].length) / + questionsByCategories[category].length + + const isCompleted = completion === 1 + const isStarted = completion > 0 + const isCurrent = currentCategory === category + + const { formattedValue, unit } = formatFootprint(numericValue, { + t, + locale, + }) + + return ( +
    +
    +
    + + {icons} + + {title}{' '} +
    + + {formattedValue} {unit} + +
    + ) +} diff --git a/src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/CategoryIllustration.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/CategoryIllustration.tsx new file mode 100644 index 000000000..27934a530 --- /dev/null +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/CategoryIllustration.tsx @@ -0,0 +1,71 @@ +'use client' + +import { useClientTranslation } from '@/hooks/useClientTranslation' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import Image from 'next/image' +import { useMemo } from 'react' +import { twMerge } from 'tailwind-merge' + +export default function CategoryIllustration({ + category, + shouldHideIllustration, +}: { + category: DottedName + shouldHideIllustration?: boolean +}) { + const { t } = useClientTranslation() + + const categoryProps = useMemo(() => { + switch (category) { + case 'transport': + return { + src: '/images/illustrations/mother-and-son-on-bike.svg', + alt: t('Une mère et son enfant sur un vélo'), + className: '', + } + case 'alimentation': + return { + src: '/images/illustrations/girl-cooking.svg', + alt: t('Une fille qui cuisine'), + className: '', + } + case 'logement': + return { + src: '/images/illustrations/girl-reading-newspaper.svg', + alt: t('Une fille qui lit un journal'), + className: 'min-w-[200px]', + } + case 'divers': + return { + src: '/images/illustrations/at-the-cinema.svg', + alt: t('Un grand-père et sa petite fille qui regardent un film'), + className: '', + } + case 'services sociétaux': + return { + src: '/images/illustrations/children-holding-hand.svg', + alt: t('Des enfants qui se tiennent la main'), + className: '', + } + default: + return null + } + }, [category, t]) + + if (!categoryProps || shouldHideIllustration) { + return null + } + + return ( + {categoryProps.alt} + ) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx b/src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx similarity index 56% rename from src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx rename to src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx index 10d43ad0a..3c003d74b 100644 --- a/src/app/(layout-with-navigation)/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx +++ b/src/app/(simulation)/simulateur/[root]/_components/simulateur/summary/Question.tsx @@ -4,22 +4,27 @@ import ChoicesValue from '@/components/misc/ChoicesValue' import NumberValue from '@/components/misc/NumberValue' import { simulateurClickSommaireQuestion } from '@/constants/tracking/pages/simulateur' import { foldEveryQuestionsUntil } from '@/helpers/foldEveryQuestionsUntil' -import { getBackgroundColor } from '@/helpers/getCategoryColorClass' +import { + getBackgroundColor, + getBackgroundLightColor, + getBorderColor, + getTextDarkColor, +} from '@/helpers/getCategoryColorClass' import { useDebug } from '@/hooks/useDebug' import { useCurrentSimulation, useForm, useRule } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { twMerge } from 'tailwind-merge' -type Props = { - question: string +export default function Question({ + question, + toggleQuestionList, + index, +}: { + question: DottedName toggleQuestionList: () => void -} - -const statusClassNames = { - missing: 'bg-gray-100 text-gray-500', - current: 'border-2 border-primary-700 bg-primary-300', - default: 'bg-primary-200', -} -export default function Question({ question, toggleQuestionList }: Props) { + index: number +}) { const { label, isMissing, @@ -31,7 +36,7 @@ export default function Question({ question, toggleQuestionList }: Props) { category, } = useRule(question) - const { updateCurrentSimulation } = useCurrentSimulation() + const { updateCurrentSimulation, foldedSteps } = useCurrentSimulation() const { currentQuestion, setCurrentQuestion, relevantQuestions } = useForm() @@ -40,10 +45,20 @@ export default function Question({ question, toggleQuestionList }: Props) { const status = currentQuestion === question ? 'current' : isFolded ? 'default' : 'missing' + const isFirstQuestionWithPristineForm = + foldedSteps?.length === 0 && index === 0 + return (
    -
    +

    Le climat se réchauffe à cause des activités humaines, c'est @@ -52,7 +52,7 @@ export default function AutresQuestions() { width={100} height={100} /> -

    +

    Pour estimer sa propre contribution au réchauffement de la planète (son "impact climat"), il est d'usage de calculer ce @@ -72,7 +72,7 @@ export default function AutresQuestions() { }> Comment on la mesure ? -

    +

    Avec une unité au nom barbare : l'équivalent CO₂. Le dioxyde @@ -104,7 +104,7 @@ export default function AutresQuestions() {

    -
    +
    trackEvent( @@ -142,7 +142,7 @@ export default function AutresQuestions() {
  • -
    +
    @@ -166,7 +166,7 @@ export default function AutresQuestions() {
  • -
    +
    @@ -194,7 +194,8 @@ export default function AutresQuestions() { Consultez la FAQ diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/AvantDeCommencer.tsx b/src/app/(simulation)/tutoriel/_components/AvantDeCommencer.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/AvantDeCommencer.tsx rename to src/app/(simulation)/tutoriel/_components/AvantDeCommencer.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/ButtonBack.tsx b/src/app/(simulation)/tutoriel/_components/ButtonBack.tsx similarity index 76% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/ButtonBack.tsx rename to src/app/(simulation)/tutoriel/_components/ButtonBack.tsx index 9b1b7eecb..4eaef46ed 100644 --- a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/ButtonBack.tsx +++ b/src/app/(simulation)/tutoriel/_components/ButtonBack.tsx @@ -1,5 +1,6 @@ 'use client' +import Trans from '@/components/translation/Trans' import { tutorielClickPrecedent } from '@/constants/tracking/pages/tutoriel' import ButtonLink from '@/design-system/inputs/ButtonLink' import { useClientTranslation } from '@/hooks/useClientTranslation' @@ -13,9 +14,8 @@ export default function ButtonBack() { href="/" color="secondary" title={t("revenir à l'accueil")} - onClick={() => trackEvent(tutorielClickPrecedent)} - className="h-12 w-12"> - ← + onClick={() => trackEvent(tutorielClickPrecedent)}> + ← Précédent ) } diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/ButtonStart.tsx b/src/app/(simulation)/tutoriel/_components/ButtonStart.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/ButtonStart.tsx rename to src/app/(simulation)/tutoriel/_components/ButtonStart.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/OrganisationMessage.tsx b/src/app/(simulation)/tutoriel/_components/OrganisationMessage.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/OrganisationMessage.tsx rename to src/app/(simulation)/tutoriel/_components/OrganisationMessage.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/autresQuestions/OrganisationPrivacy.tsx b/src/app/(simulation)/tutoriel/_components/autresQuestions/OrganisationPrivacy.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/autresQuestions/OrganisationPrivacy.tsx rename to src/app/(simulation)/tutoriel/_components/autresQuestions/OrganisationPrivacy.tsx diff --git a/src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/avantDeCommencer/OrganisationDisclaimer.tsx b/src/app/(simulation)/tutoriel/_components/avantDeCommencer/OrganisationDisclaimer.tsx similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/tutoriel/_components/avantDeCommencer/OrganisationDisclaimer.tsx rename to src/app/(simulation)/tutoriel/_components/avantDeCommencer/OrganisationDisclaimer.tsx diff --git a/src/app/(simulation)/tutoriel/page.tsx b/src/app/(simulation)/tutoriel/page.tsx new file mode 100644 index 000000000..8823bce93 --- /dev/null +++ b/src/app/(simulation)/tutoriel/page.tsx @@ -0,0 +1,71 @@ +import Trans from '@/components/translation/Trans' +import Title from '@/design-system/layout/Title' +import ButtonStart from './_components/ButtonStart' + +import ContentLarge from '@/components/layout/ContentLarge' +import { noIndexObject } from '@/constants/metadata' +import { getServerTranslation } from '@/helpers/getServerTranslation' +import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' +import { twMerge } from 'tailwind-merge' +import AutresQuestions from './_components/AutresQuestions' +import AvantDeCommencer from './_components/AvantDeCommencer' +import ButtonBack from './_components/ButtonBack' +import OrganisationMessage from './_components/OrganisationMessage' + +export async function generateMetadata() { + const { t } = await getServerTranslation() + + return getMetadataObject({ + title: t( + 'Calculer votre empreinte carbone individuelle - Nos Gestes Climat' + ), + description: t( + 'Comprenez comment calculer votre empreinte sur le climat en 10min chrono.' + ), + alternates: { + canonical: '/tutoriel', + }, + robots: noIndexObject, + }) +} + +export default async function Tutoriel() { + return ( + +
    + + <span className="inline text-secondary-700"> + <Trans>10 minutes</Trans> + </span>{' '} + <Trans>chrono pour calculer votre empreinte carbone et eau</Trans> + </> + } + /> + + <AvantDeCommencer /> + + <AutresQuestions /> + + <div + className={twMerge( + 'fixed bottom-0 left-0 right-0 z-50 border-t-2 border-primary-200 bg-gray-100 py-3' + )}> + <div + className={twMerge( + 'relative mx-auto flex w-full max-w-3xl justify-between gap-4 px-4 md:px-0 lg:justify-start' + )}> + <ButtonBack /> + + <OrganisationMessage /> + + <ButtonStart /> + </div> + </div> + </div> + </ContentLarge> + ) +} diff --git a/src/app/_components/mainLayoutProviders/MainHooks.tsx b/src/app/_components/mainLayoutProviders/MainHooks.tsx index f493d5ff6..07fac2044 100644 --- a/src/app/_components/mainLayoutProviders/MainHooks.tsx +++ b/src/app/_components/mainLayoutProviders/MainHooks.tsx @@ -5,6 +5,8 @@ */ 'use client' +import Loader from '@/design-system/layout/Loader' +import { useSetCurrentSimulationFromParams } from '@/hooks/simulation/useSetCurrentSimulationFromParams' import { useTrackLocale } from '@/hooks/tracking/useTrackLocale' import { useTrackPageView } from '@/hooks/tracking/useTrackPageView' import { useTrackRegion } from '@/hooks/tracking/useTrackRegion' @@ -23,5 +25,15 @@ export default function MainHooks({ children }: PropsWithChildren) { useUserInfosParams() useInitSimulationParam() + // Set the current simulation from the URL params (if applicable) + const { isCorrectSimulationSet } = useSetCurrentSimulationFromParams() + + if (!isCorrectSimulationSet) + return ( + <div className="flex h-screen flex-1 items-center justify-center"> + <Loader color="dark" /> + </div> + ) + return children } diff --git a/src/app/documentation/[...slug]/_components/documentationRouter/DocumentationServer.tsx b/src/app/documentation/[...slug]/_components/documentationRouter/DocumentationServer.tsx index 1224e4329..2914235ae 100644 --- a/src/app/documentation/[...slug]/_components/documentationRouter/DocumentationServer.tsx +++ b/src/app/documentation/[...slug]/_components/documentationRouter/DocumentationServer.tsx @@ -6,10 +6,9 @@ import Markdown from '@/design-system/utils/Markdown' import { getGeolocation } from '@/helpers/getGeolocation' import { getRules } from '@/helpers/modelFetching/getRules' import { getRuleTitle } from '@/helpers/publicodes/getRuleTitle' -import { Rules } from '@/publicodes-state/types' import { capitalizeString } from '@/utils/capitalizeString' import { decodeRuleNameFromPath } from '@/utils/decodeRuleNameFromPath' -import { SupportedRegions } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, SupportedRegions } from '@incubateur-ademe/nosgestesclimat' import { currentLocale } from 'next-i18n-router' import { redirect } from 'next/navigation' import ButtonLaunch from './documentationServer/ButtonLaunch' @@ -23,7 +22,7 @@ type Props = { locale?: string } export default async function DocumentationServer({ slugs }: Props) { - const ruleName = decodeRuleNameFromPath(slugs.join('/')) + const ruleName = decodeRuleNameFromPath(slugs.join('/')) as DottedName const region = await getGeolocation() @@ -34,7 +33,7 @@ export default async function DocumentationServer({ slugs }: Props) { } // We load the default rules to render the server side documentation - const rules: Rules = await getRules({ + const rules = await getRules({ isOptim: false, locale, regionCode: region?.code, diff --git a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/CalculDetail.tsx b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/CalculDetail.tsx index d42be5899..00f61b530 100644 --- a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/CalculDetail.tsx +++ b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/CalculDetail.tsx @@ -1,7 +1,10 @@ import Trans from '@/components/translation/Trans' import Card from '@/design-system/layout/Card' -import { Rules } from '@/publicodes-state/types' -import { Rule } from 'publicodes' +import { + DottedName, + NGCRule, + NGCRules, +} from '@incubateur-ademe/nosgestesclimat' import RuleDetail from './calculDetail/RuleDetail' export default function CalculDetail({ @@ -9,9 +12,9 @@ export default function CalculDetail({ ruleName, rules, }: { - rule: Rule - ruleName: string - rules: Rules + rule: NGCRule + ruleName: DottedName + rules: NGCRules }) { return ( <> diff --git a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/PagesProches.tsx b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/PagesProches.tsx index 7b63339a9..ce6369f76 100644 --- a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/PagesProches.tsx +++ b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/PagesProches.tsx @@ -1,18 +1,19 @@ import Link from '@/components/Link' import { getRuleTitle } from '@/helpers/publicodes/getRuleTitle' -import { Rules } from '@/publicodes-state/types' +import { DottedName, NGCRules } from '@incubateur-ademe/nosgestesclimat' import { utils } from 'publicodes' export default function PagesProches({ rules, ruleName, }: { - rules: Rules - ruleName: string + rules: NGCRules + ruleName: DottedName }) { const namespaceRules = Object.keys(rules).filter( (key) => key.includes(ruleName) && key !== ruleName - ) + ) as DottedName[] + if (!namespaceRules.length) return null return ( <section className="mt-8"> diff --git a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/calculDetail/RuleDetail.tsx b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/calculDetail/RuleDetail.tsx index 0f30adef6..24460fa9d 100644 --- a/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/calculDetail/RuleDetail.tsx +++ b/src/app/documentation/[...slug]/_components/documentationRouter/documentationServer/calculDetail/RuleDetail.tsx @@ -1,7 +1,7 @@ import Link from '@/components/Link' -import { DottedName, Rules } from '@/publicodes-state/types' import { capitalizeString } from '@/utils/capitalizeString' import { encodeRuleName } from '@/utils/publicodes/encodeRuleName' +import { DottedName, NGCRules } from '@incubateur-ademe/nosgestesclimat' import { Rule, utils } from 'publicodes' const KEYS_TO_OMIT = [ @@ -42,7 +42,7 @@ export default function RuleDetail({ ruleData: Rule | string | number context: { dottedName: DottedName - rules: Rules + rules: NGCRules } }) { const isDataObject = @@ -60,7 +60,8 @@ export default function RuleDetail({ if (!context) return <span>{capitalizeString(ruleFormatted)}</span> const ruleString = utils.disambiguateReference( - context.rules, + // Should be ParsedRules but not available from server side. + context.rules as any, context.dottedName, ruleFormatted ) diff --git a/src/app/documentation/_components/DocumentationLanding.tsx b/src/app/documentation/_components/DocumentationLanding.tsx index 38407228e..fd9a09e6b 100644 --- a/src/app/documentation/_components/DocumentationLanding.tsx +++ b/src/app/documentation/_components/DocumentationLanding.tsx @@ -7,6 +7,7 @@ import { useRules } from '@/hooks/useRules' import Link from '@/components/Link' import LightBulbIcon from '@/components/icons/LightBulbIcon' import { useClientTranslation } from '@/hooks/useClientTranslation' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Image from 'next/image' import { useRef } from 'react' import SearchBar from './SearchBar' @@ -35,7 +36,7 @@ export default function DocumentationLanding() { 'transport . voiture': t( `Le premier poste moyen d'empreinte, l'incontournable **voiture individuelle**` ), - } as Record<string, string>) + } as Record<DottedName, string>) if (!rules) return null @@ -85,17 +86,19 @@ export default function DocumentationLanding() { </h2> <ul className="grid max-w-[60rem] grid-cols-1 flex-wrap gap-2 p-0 sm:grid-cols-2 md:grid-cols-3"> - {Object.keys(fixedCardSummaries.current).map((dottedName) => { - return ( - <li key={dottedName}> - <DocumentationLandingCard - dottedName={dottedName} - summary={fixedCardSummaries.current[dottedName]} - rule={rules[dottedName]} - /> - </li> - ) - })} + {(Object.keys(fixedCardSummaries.current) as DottedName[]).map( + (dottedName) => { + return ( + <li key={dottedName}> + <DocumentationLandingCard + dottedName={dottedName} + summary={fixedCardSummaries.current[dottedName]} + rule={rules[dottedName]} + /> + </li> + ) + } + )} </ul> </div> ) diff --git a/src/app/documentation/_components/SearchBar.tsx b/src/app/documentation/_components/SearchBar.tsx index 2dfbeeadd..2653c7f19 100644 --- a/src/app/documentation/_components/SearchBar.tsx +++ b/src/app/documentation/_components/SearchBar.tsx @@ -3,8 +3,7 @@ import Trans from '@/components/translation/Trans' import Card from '@/design-system/layout/Card' import { getRuleTitle } from '@/helpers/publicodes/getRuleTitle' import { useClientTranslation } from '@/hooks/useClientTranslation' -import { DottedName } from '@/publicodes-state/types' -import { NGCRules } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRules } from '@incubateur-ademe/nosgestesclimat' import Fuse from 'fuse.js' import { utils } from 'publicodes' import { useEffect, useMemo, useRef, useState } from 'react' diff --git a/src/app/documentation/_components/documentationLanding/DocumentationLandingCard.tsx b/src/app/documentation/_components/documentationLanding/DocumentationLandingCard.tsx index de8d68c5b..66a2e5d3e 100644 --- a/src/app/documentation/_components/documentationLanding/DocumentationLandingCard.tsx +++ b/src/app/documentation/_components/documentationLanding/DocumentationLandingCard.tsx @@ -9,8 +9,7 @@ import { getTextDarkColor, } from '@/helpers/getCategoryColorClass' import { useEngine } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' -import { NGCRule } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRule } from '@incubateur-ademe/nosgestesclimat' import Markdown from 'markdown-to-jsx' import { utils } from 'publicodes' diff --git a/src/app/documentation/layout.tsx b/src/app/documentation/layout.tsx index a3e277cac..0929993e1 100644 --- a/src/app/documentation/layout.tsx +++ b/src/app/documentation/layout.tsx @@ -1,4 +1,3 @@ -import Footer from '@/components/layout/Footer' import Main from '@/design-system/layout/Main' import { PropsWithChildren } from 'react' import { IsDocumentationClientProvider } from './_contexts/DocumentationStateContext' @@ -11,8 +10,6 @@ export default function Layout({ children }: PropsWithChildren) { {children} </div> </Main> - - <Footer /> </IsDocumentationClientProvider> ) } diff --git a/src/app/globals.css b/src/app/globals.css index 64a584a3a..77e7c1857 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,6 +2,48 @@ @tailwind components; @tailwind utilities; +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-Thin.woff2') format('woff2'); + font-weight: 100; + font-style: normal; +} + +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-Light.woff2') format('woff2'); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-Regular.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-Medium.woff2') format('woff2'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-Bold.woff2') format('woff2'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Marianne'; + src: url('/fonts/Marianne-ExtraBold.woff2') format('woff2'); + font-weight: 800; + font-style: normal; +} + @layer base { input[type='number']::-webkit-outer-spin-button, input[type='number']::-webkit-inner-spin-button, @@ -516,8 +558,14 @@ html[data-useragent*='Trident'] #js { } .notification blockquote { - background-color: #fff; + background-color: inherit; + color: inherit; padding: 0; + margin: 0; +} + +.notification blockquote > p { + @apply mb-2; } .island { @@ -548,4 +596,49 @@ html[data-useragent*='Trident'] #js { --toastify-color-success: #15803d !important; --toastify-icon-color-success: #15803d !important; --toastify-text-color-success: #15803d !important; + --toastify-color-error: rgb(185 28 28) !important; + --toastify-icon-color-error: rgb(185 28 28) !important; + --toastify-text-color-error: rgb(185 28 28) !important; + --toastify-font-family: 'Marianne', sans-serif !important; + --toastify-text-color-light: #1a1a1a !important; + --toastify-toast-bd-radius: 0.5rem !important; +} + +.Toastify__toast--error { + @apply bg-red-50 text-red-700 !important; +} +.Toastify__close-button--light { + opacity: 1 !important; + @apply text-gray-700 !important; +} +.Toastify__progress-bar { + background: linear-gradient( + 270deg, + #a2d2fd, + #cef1d5, + #ffefbc, + #ffdbbc, + #fd69d0, + #a2d2fd, + #cef1d5, + #ffefbc, + #ffdbbc, + #fd69d0, + #a2d2fd + ) !important; + background-size: 200% 200% !important; +} + +.Toastify__toast-icon { + display: none !important; +} + +.Toastify__progress-bar--bg { + @apply bg-gray-100 !important; + opacity: 1 !important; +} + +.animation-once { + animation-iteration-count: 1; + animation-fill-mode: forwards; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ce1d09708..045d4a5cf 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,49 +1,46 @@ -import FilAriane from '@/components/layout/FilAriane' -import Header from '@/components/layout/Header' import { getGeolocation } from '@/helpers/getGeolocation' import { getMigrationInstructions } from '@/helpers/modelFetching/getMigrationInstructions' // Initialise react-i18next +import Footer from '@/components/layout/Footer' import '@/locales/initClient' import '@/locales/initServer' -import { ErrorBoundary } from '@sentry/nextjs' import { dir } from 'i18next' import { currentLocale } from 'next-i18n-router' import localFont from 'next/font/local' import Script from 'next/script' import { PropsWithChildren } from 'react' -import { ErrorFallback } from './_components/ErrorFallback' import MainLayoutProviders from './_components/MainLayoutProviders' import './globals.css' export const marianne = localFont({ src: [ { - path: '_fonts/Marianne-Thin.woff2', + path: '../../public/fonts/Marianne-Thin.woff2', weight: '100', style: 'normal', }, { - path: '_fonts/Marianne-Light.woff2', + path: '../../public/fonts/Marianne-Light.woff2', weight: '300', style: 'normal', }, { - path: '_fonts/Marianne-Regular.woff2', + path: '../../public/fonts/Marianne-Regular.woff2', weight: 'normal', style: 'normal', }, { - path: '_fonts/Marianne-Medium.woff2', + path: '../../public/fonts/Marianne-Medium.woff2', weight: '500', style: 'normal', }, { - path: '_fonts/Marianne-Bold.woff2', + path: '../../public/fonts/Marianne-Bold.woff2', weight: 'bold', style: 'normal', }, { - path: '_fonts/Marianne-ExtraBold.woff2', + path: '../../public/fonts/Marianne-ExtraBold.woff2', weight: '800', style: 'normal', }, @@ -59,7 +56,7 @@ export default async function RootLayout({ children }: PropsWithChildren) { return ( <html lang={lang ?? ''} dir={dir(lang ?? '')}> <head> - <link rel="icon" href="/images/misc/favicon.png" /> + <link rel="icon" href="/favicon.png" /> <meta name="google-site-verification" @@ -96,23 +93,19 @@ export default async function RootLayout({ children }: PropsWithChildren) { )} </head> - <body className={`${marianne.className} bg-white text-default`}> + <body + className={`${marianne.className} bg-white text-default transition-colors duration-700`}> <Script id="script-user-agent">{` const b = document.documentElement; b.setAttribute('data-useragent', navigator.userAgent); `}</Script> - <ErrorBoundary showDialog fallback={ErrorFallback}> - <MainLayoutProviders - region={region} - migrationInstructions={migrationInstructions}> - <Header /> - - <FilAriane /> - - {children} - </MainLayoutProviders> - </ErrorBoundary> + <MainLayoutProviders + region={region} + migrationInstructions={migrationInstructions}> + {children} + <Footer /> + </MainLayoutProviders> <div id="modal" /> </body> diff --git a/src/app/page.tsx b/src/app/page.tsx index 7ff46dc4c..dcb159a19 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -import Footer from '@/components/layout/Footer' +import Header from '@/components/layout/Header' import Main from '@/design-system/layout/Main' import { getServerTranslation } from '@/helpers/getServerTranslation' import { getMetadataObject } from '@/helpers/metadata/getMetadataObject' @@ -27,7 +27,8 @@ export async function generateMetadata() { export default async function Homepage() { return ( <> - <Main> + <Header /> + <Main className="lg:-mt-8"> <Heading /> <div className="mx-auto mb-12 flex w-full max-w-5xl flex-col flex-wrap items-center gap-12 px-4 md:mb-20 md:flex-row md:items-start md:px-8 lg:gap-28"> <Amis /> @@ -37,7 +38,6 @@ export default async function Homepage() { <Explanations /> <Contributions /> </Main> - <Footer className="bg-white" /> </> ) } diff --git a/src/components/actions/howToAct/RecommendedActions.tsx b/src/components/actions/howToAct/RecommendedActions.tsx index 2b69b8f14..817fb8776 100644 --- a/src/components/actions/howToAct/RecommendedActions.tsx +++ b/src/components/actions/howToAct/RecommendedActions.tsx @@ -1,7 +1,7 @@ 'use client' import { useActions, useEngine } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' import RecommendedAction from './recommendedActions/RecommendedAction' @@ -13,7 +13,7 @@ export default function RecommendedActions() { () => orderedActionDottedNames .reduce( - (accumulator: string[], currentActionDottedName: DottedName) => { + (accumulator: DottedName[], currentActionDottedName: DottedName) => { // We don't want to display the "services sociétaux" category if (currentActionDottedName.includes('services sociétaux')) { return accumulator diff --git a/src/components/actions/howToAct/recommendedActions/RecommendedAction.tsx b/src/components/actions/howToAct/recommendedActions/RecommendedAction.tsx index 70a8dc79f..63ec88120 100644 --- a/src/components/actions/howToAct/recommendedActions/RecommendedAction.tsx +++ b/src/components/actions/howToAct/recommendedActions/RecommendedAction.tsx @@ -8,8 +8,8 @@ import { getBorderColor, } from '@/helpers/getCategoryColorClass' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' export default function RecommendedAction({ actionDottedName, diff --git a/src/components/charts/HorizontalBarChartItem.tsx b/src/components/charts/HorizontalBarChartItem.tsx index 87d2c1670..2356e6065 100644 --- a/src/components/charts/HorizontalBarChartItem.tsx +++ b/src/components/charts/HorizontalBarChartItem.tsx @@ -6,6 +6,7 @@ type Props = { title?: string icons?: string displayValue: ReactNode + shouldDisplayValue?: boolean percentageOfTotalValue: number minTitleWidth?: number index?: number @@ -16,33 +17,30 @@ export default function HorizontalBarChartItem({ title, icons, displayValue, + shouldDisplayValue = true, percentageOfTotalValue, minTitleWidth, index, barColor, }: Props) { return ( - <div className="flex w-full items-center justify-between gap-2"> + <div className="w-full"> <div - className="hidden items-center gap-2 text-sm md:flex" + className="mb-1.5 flex justify-between text-sm" style={{ minWidth: (minTitleWidth ?? 10) + 'rem', }}> - <Emoji>{icons}</Emoji>{' '} - <p className={`mb-0 underline decoration-dotted underline-offset-4`}> - {title} - </p> + <div className="flex items-center gap-1"> + <Emoji>{icons}</Emoji> <p className={`mb-0`}>{title}</p> + </div> + {shouldDisplayValue && ( + <div className="mr-4 min-w-20 text-right text-primary-700"> + {displayValue} + </div> + )} </div> - <div className="flex items-center gap-2 md:hidden"> - <Emoji>{icons}</Emoji>{' '} - <p - className={`mb-0 text-sm underline decoration-dotted underline-offset-4`}> - {(title?.length ?? 0) > 14 ? title?.split(' ')[0] : title} - </p> - </div> - - <div className="mr-4 hidden flex-1 md:block"> + <div className="mr-4 flex-1"> <BarChart type="horizontal" value={`${percentageOfTotalValue}%`} @@ -50,10 +48,6 @@ export default function HorizontalBarChartItem({ color={barColor} /> </div> - - <div className="mr-4 min-w-20 text-right text-sm text-primary-700"> - {displayValue} - </div> </div> ) } diff --git a/src/components/charts/RavijenChart.tsx b/src/components/charts/RavijenChart.tsx index 6fee8f77b..3644920ca 100644 --- a/src/components/charts/RavijenChart.tsx +++ b/src/components/charts/RavijenChart.tsx @@ -5,12 +5,13 @@ import { trackingDownloadRavijenChart } from '@/constants/tracking/misc' import Button from '@/design-system/inputs/Button' import { useEngine } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { toPng } from 'html-to-image' import CategoryChart from './ravijenChart/CategoryChart' type Props = { - categories: string[] - subcategories: { [key: string]: string[] } + categories: DottedName[] + subcategories: Record<DottedName, DottedName[]> squashLimitPercentage?: number isInverted?: boolean shouldAlwaysDisplayValue?: boolean diff --git a/src/components/charts/ServicesChart.tsx b/src/components/charts/ServicesChart.tsx index 5a486d176..8f208ac5f 100644 --- a/src/components/charts/ServicesChart.tsx +++ b/src/components/charts/ServicesChart.tsx @@ -1,4 +1,5 @@ import { useEngine } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import RavijenChart from './RavijenChart' export default function ServicesChart() { @@ -7,11 +8,11 @@ export default function ServicesChart() { const serviceCategories = getSubcategories('services sociétaux') const serviceSubcategories = serviceCategories.reduce( - (acc: { [key: string]: string[] }, category: string) => { + (acc, category) => { acc[category] = getSubcategories(category) return acc }, - {} + {} as Record<DottedName, DottedName[]> ) return ( diff --git a/src/components/charts/ravijenChart/CategoryChart.tsx b/src/components/charts/ravijenChart/CategoryChart.tsx index 927ece4f5..e0739b7d8 100644 --- a/src/components/charts/ravijenChart/CategoryChart.tsx +++ b/src/components/charts/ravijenChart/CategoryChart.tsx @@ -2,12 +2,13 @@ import { DEFAULT_LIMIT_PERCENTAGE_TO_SQUASH } from '@/constants/ravijen' import { useEngine } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import SubcategoryChartBlock from './categoryChart/SubcategoryChartBlock' import TotalCategoryBlock from './categoryChart/TotalCategoryBlock' type Props = { - category: string - subcategories: string[] + category: DottedName + subcategories: DottedName[] maxValue: number squashLimitPercentage?: number isInverted?: boolean @@ -27,7 +28,7 @@ export default function CategoryChart({ let sumSquashedSubcategoriesPercentage = 0 const sortedSubcategories = subcategories - ?.filter((subcategory: string) => checkIfValid(subcategory)) + ?.filter((subcategory) => checkIfValid(subcategory)) // Get the value to display in the EnigmaticMoreChartBlock .map((subcategory) => { const categoryValue = getNumericValue(category) ?? 0 @@ -44,7 +45,7 @@ export default function CategoryChart({ } return subcategory }) - .sort((categoryA: string, categoryB: string) => { + .sort((categoryA, categoryB) => { const valueA = getNumericValue(categoryA) ?? 0 const valueB = getNumericValue(categoryB) ?? 0 @@ -60,7 +61,7 @@ export default function CategoryChart({ className={`flex h-[calc(100%-7rem)] ${ isInverted ? 'flex-col-reverse' : 'flex-col' } justify-end gap-[1px]`}> - {sortedSubcategories?.map((subcategory: string, index: number) => { + {sortedSubcategories?.map((subcategory, index: number) => { return ( <SubcategoryChartBlock key={subcategory} diff --git a/src/components/charts/ravijenChart/categoryChart/SubcategoryChartBlock.tsx b/src/components/charts/ravijenChart/categoryChart/SubcategoryChartBlock.tsx index 993e6502f..4450cd29f 100644 --- a/src/components/charts/ravijenChart/categoryChart/SubcategoryChartBlock.tsx +++ b/src/components/charts/ravijenChart/categoryChart/SubcategoryChartBlock.tsx @@ -8,11 +8,12 @@ import { getBackgroundColor } from '@/helpers/getCategoryColorClass' import { useRule } from '@/publicodes-state' import { capitalizeString } from '@/utils/capitalizeString' import { removePercentageFromString } from '@/utils/removePercentageFromString' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import EnigmaticMoreChartBlock from './subcategoryChartBlock/EnigmaticMoreChartBlock' type Props = { - category: string - subcategory: string + category: DottedName + subcategory: DottedName maxValue: number index: number squashLimitPercentage?: number diff --git a/src/components/charts/ravijenChart/categoryChart/TotalCategoryBlock.tsx b/src/components/charts/ravijenChart/categoryChart/TotalCategoryBlock.tsx index a44003049..b6190297b 100644 --- a/src/components/charts/ravijenChart/categoryChart/TotalCategoryBlock.tsx +++ b/src/components/charts/ravijenChart/categoryChart/TotalCategoryBlock.tsx @@ -3,9 +3,14 @@ import Link from '@/components/Link' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Image from 'next/image' -export default function TotalCategoryBlock({ category }: { category: string }) { +export default function TotalCategoryBlock({ + category, +}: { + category: DottedName +}) { const { numericValue: totalNumericValue } = useRule('bilan') const { title, numericValue } = useRule(category) diff --git a/src/components/charts/ravijenChart/categoryChart/subcategoryChartBlock/EnigmaticMoreChartBlock.tsx b/src/components/charts/ravijenChart/categoryChart/subcategoryChartBlock/EnigmaticMoreChartBlock.tsx index 14305b4dc..15ec2bf44 100644 --- a/src/components/charts/ravijenChart/categoryChart/subcategoryChartBlock/EnigmaticMoreChartBlock.tsx +++ b/src/components/charts/ravijenChart/categoryChart/subcategoryChartBlock/EnigmaticMoreChartBlock.tsx @@ -1,8 +1,9 @@ 'use client' import { getBackgroundColor } from '@/helpers/getCategoryColorClass' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' -type Props = { category: string; percentageSquashed: number } +type Props = { category: DottedName; percentageSquashed: number } export default function EnigmaticMoreChartBlock({ category, diff --git a/src/components/fin/mainSubcategories/MainSubcategory.tsx b/src/components/fin/mainSubcategories/MainSubcategory.tsx index dd34565ac..de9340886 100644 --- a/src/components/fin/mainSubcategories/MainSubcategory.tsx +++ b/src/components/fin/mainSubcategories/MainSubcategory.tsx @@ -8,7 +8,7 @@ import { useClientTranslation } from '@/hooks/useClientTranslation' import { useCurrentMetric } from '@/hooks/useCurrentMetric' import { useLocale } from '@/hooks/useLocale' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' const widthClassName = ['w-full', 'w-11/12 md:w-3/4', 'w-10/12 md:w-1/2'] diff --git a/src/components/fin/metricSlider/TabNavigation.tsx b/src/components/fin/metricSlider/TabNavigation.tsx index b94c5b195..3fd6cd499 100644 --- a/src/components/fin/metricSlider/TabNavigation.tsx +++ b/src/components/fin/metricSlider/TabNavigation.tsx @@ -97,7 +97,7 @@ export default function TabNavigation({ </button> )} </div> - {!isStatic && <HeadingButtons endPage />} + {!isStatic && <HeadingButtons />} </div> ) } diff --git a/src/components/fin/metricSlider/waterTotalChart/WaveContent.tsx b/src/components/fin/metricSlider/waterTotalChart/WaveContent.tsx index 26ff00646..15d61d61d 100644 --- a/src/components/fin/metricSlider/waterTotalChart/WaveContent.tsx +++ b/src/components/fin/metricSlider/waterTotalChart/WaveContent.tsx @@ -7,7 +7,7 @@ type Props = { } export default function WaveContent({ isStatic }: Props) { return ( - <div className="relative overflow-hidden rounded-b-xl px-4 pb-6 pt-12 transition-opacity lg:px-36 lg:pb-8 lg:pt-14"> + <div className="relative overflow-hidden rounded-b-xl px-4 pb-6 pt-12 transition-opacity lg:pb-8 lg:pt-14"> <Wave fill="#5152D0" className="pointer-events-none absolute bottom-0 left-0 right-0 h-full w-full" @@ -19,7 +19,7 @@ export default function WaveContent({ isStatic }: Props) { }} /> <Octopus /> - <p className="relative mb-0 text-xs italic text-white lg:text-base"> + <p className="relative mx-auto mb-0 max-w-[44rem] text-xs italic text-white lg:text-base"> {isStatic ? ( <Trans> L'empreinte eau, c'est l'ensemble de l'eau consommée pour produire diff --git a/src/components/fin/metricSlider/waterTotalChart/waveContent/Octopus.tsx b/src/components/fin/metricSlider/waterTotalChart/waveContent/Octopus.tsx index c325b271f..cae0cfc80 100644 --- a/src/components/fin/metricSlider/waterTotalChart/waveContent/Octopus.tsx +++ b/src/components/fin/metricSlider/waterTotalChart/waveContent/Octopus.tsx @@ -3,7 +3,7 @@ import Emoji from '@/design-system/utils/Emoji' export default function Octopus() { return ( <div className="absolute bottom-2 left-0 right-0 w-full"> - <Emoji className="animate-swim-delay ">🦑</Emoji> + <Emoji className="block animate-swim-delay ">🦑</Emoji> </div> ) } diff --git a/src/components/form/Navigation.tsx b/src/components/form/Navigation.tsx index f071b8a1b..58d3944fa 100644 --- a/src/components/form/Navigation.tsx +++ b/src/components/form/Navigation.tsx @@ -13,21 +13,23 @@ import Button from '@/design-system/inputs/Button' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useMagicKey } from '@/hooks/useMagicKey' import { useCurrentSimulation, useForm, useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { MouseEvent, useCallback, useMemo } from 'react' - -type Props = { - question: DottedName - tempValue?: number - onComplete?: () => void -} +import { twMerge } from 'tailwind-merge' +import SyncIndicator from './navigation/SyncIndicator' export default function Navigation({ question, tempValue, onComplete = () => '', -}: Props) { + isEmbedded, +}: { + question: DottedName + tempValue?: number + onComplete?: () => void + isEmbedded?: boolean +}) { const { t } = useClientTranslation() const { gotoPrevQuestion, gotoNextQuestion, noPrevQuestion, noNextQuestion } = @@ -115,8 +117,18 @@ export default function Navigation({ } return ( - <div className="flex justify-end md:gap-4"> - {!noPrevQuestion ? ( + <div + className={twMerge( + 'fixed bottom-0 left-0 right-0 z-50 bg-gray-100 py-3', + isEmbedded && 'static bg-primary-100 p-0' + )}> + <SyncIndicator /> + + <div + className={twMerge( + 'relative mx-auto flex w-full max-w-6xl justify-between gap-4 px-4 lg:justify-start', + isEmbedded && 'justify-start' + )}> <Button size="md" onClick={() => { @@ -128,22 +140,25 @@ export default function Navigation({ handleMoveFocus() }} - color="text"> + disabled={noPrevQuestion} + color="text" + className={twMerge('px-3')}> {'← ' + t('Précédent')} </Button> - ) : null} - <Button - color={isMissing ? 'secondary' : 'primary'} - disabled={isNextDisabled} - size="md" - data-cypress-id="next-question-button" - onClick={handleGoToNextQuestion}> - {noNextQuestion - ? t('Terminer') - : isMissing - ? t('Je ne sais pas') + ' →' - : t('Suivant') + ' →'} - </Button> + + <Button + color={isMissing ? 'secondary' : 'primary'} + disabled={isNextDisabled} + size="md" + data-cypress-id="next-question-button" + onClick={handleGoToNextQuestion}> + {noNextQuestion + ? t('Terminer') + : isMissing + ? t('Passer la question') + ' →' + : t('Suivant') + ' →'} + </Button> + </div> </div> ) } diff --git a/src/components/form/Question.tsx b/src/components/form/Question.tsx index 7883b77b5..27e515be8 100644 --- a/src/components/form/Question.tsx +++ b/src/components/form/Question.tsx @@ -16,18 +16,31 @@ import { questionChooseAnswer, questionTypeAnswer, } from '@/constants/tracking/question' +import Button from '@/design-system/inputs/Button' import { useRule } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' -import { useEffect, useRef } from 'react' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { useEffect, useRef, useState } from 'react' +import { twMerge } from 'tailwind-merge' +import Trans from '../translation/Trans' +import Category from './question/Category' import Warning from './question/Warning' type Props = { - question: string + question: DottedName tempValue?: number | undefined setTempValue?: (value: number | undefined) => void + showInputsLabel?: React.ReactNode | string + className?: string } -export default function Question({ question, tempValue, setTempValue }: Props) { +export default function Question({ + question, + tempValue, + setTempValue, + showInputsLabel, + className, +}: Props) { const { type, label, @@ -60,82 +73,101 @@ export default function Question({ question, tempValue, setTempValue }: Props) { } }, [type, numericValue, setTempValue, question]) + const [isOpen, setIsOpen] = useState(showInputsLabel ? false : true) + return ( <> - <div className="mb-4"> + <div className={twMerge('mb-6 flex flex-col items-start', className)}> + <Category question={question} /> <Label question={question} label={label} description={description} /> <Suggestions question={question} setValue={(value) => { if (type === 'number') { - if (setTempValue) setTempValue(value) + if (setTempValue) setTempValue(value as number) } setValue(value, { foldedStep: question }) }} /> + {showInputsLabel ? ( + <Button + color="link" + size="xs" + onClick={() => setIsOpen((prevIsOpen) => !prevIsOpen)} + className="mb-2"> + {isOpen ? <Trans>Fermer</Trans> : showInputsLabel} + </Button> + ) : null} + {isOpen && ( + <> + {type === 'number' && ( + <NumberInput + unit={unit} + value={setTempValue ? tempValue : numericValue} + setValue={(value) => { + if (setTempValue) { + setTempValue(value) + } + setValue(value, { foldedStep: question }) + trackEvent(questionTypeAnswer({ question, answer: value })) + }} + isMissing={isMissing} + min={0} + data-cypress-id={question} + id={DEFAULT_FOCUS_ELEMENT_ID} + aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} + /> + )} - {type === 'number' && ( - <NumberInput - unit={unit} - value={setTempValue ? tempValue : numericValue} - setValue={(value) => { - if (setTempValue) { - setTempValue(value) - } - setValue(value, { foldedStep: question }) - trackEvent(questionTypeAnswer({ question, answer: value })) - }} - isMissing={isMissing} - min={0} - data-cypress-id={question} - id={DEFAULT_FOCUS_ELEMENT_ID} - aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} - /> - )} + {type === 'boolean' && ( + <BooleanInput + value={value} + setValue={(value) => { + { + setValue(value, { foldedStep: question }) + trackEvent( + questionChooseAnswer({ question, answer: value }) + ) + } + }} + isMissing={isMissing} + data-cypress-id={question} + label={label || ''} + id={DEFAULT_FOCUS_ELEMENT_ID} + aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} + /> + )} - {type === 'boolean' && ( - <BooleanInput - value={value} - setValue={(value) => { - { - setValue(value, { foldedStep: question }) - trackEvent(questionChooseAnswer({ question, answer: value })) - } - }} - isMissing={isMissing} - data-cypress-id={question} - label={label || ''} - id={DEFAULT_FOCUS_ELEMENT_ID} - aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} - /> - )} - - {type === 'choices' && ( - <ChoicesInput - question={question} - choices={choices} - value={String(value)} - setValue={(value) => { - { - setValue(value, { foldedStep: question }) - trackEvent(questionChooseAnswer({ question, answer: value })) - } - }} - isMissing={isMissing} - data-cypress-id={question} - label={label || ''} - id={DEFAULT_FOCUS_ELEMENT_ID} - aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} - /> - )} + {type === 'choices' && ( + <ChoicesInput + question={question} + choices={choices} + value={String(value)} + setValue={(value) => { + { + setValue(value, { foldedStep: question }) + trackEvent( + questionChooseAnswer({ question, answer: value }) + ) + } + }} + isMissing={isMissing} + data-cypress-id={question} + label={label || ''} + id={DEFAULT_FOCUS_ELEMENT_ID} + aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} + /> + )} - {type === 'mosaic' && ( - <Mosaic - question={question} - questionsOfMosaic={questionsOfMosaicFromParent} - aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} - /> + {type === 'mosaic' && ( + <Mosaic + question={question} + questionsOfMosaic={questionsOfMosaicFromParent} + aria-describedby={QUESTION_DESCRIPTION_BUTTON_ID} + /> + )} + </> )} </div> diff --git a/src/components/form/navigation/SyncIndicator.tsx b/src/components/form/navigation/SyncIndicator.tsx new file mode 100644 index 000000000..2f2c26a83 --- /dev/null +++ b/src/components/form/navigation/SyncIndicator.tsx @@ -0,0 +1,40 @@ +'use client' + +import { useBackgroundSyncSimulation } from '@/hooks/simulation/useBackgroundSyncSimulation' +import { motion } from 'framer-motion' +import { useEffect, useState } from 'react' + +export default function SyncIndicator() { + const { isSyncedWithBackend, saveDelay } = useBackgroundSyncSimulation() + + const [initAnimation, setInitAnimation] = useState(isSyncedWithBackend) + + useEffect(() => { + if (!isSyncedWithBackend) { + setInitAnimation(true) + } + }, [isSyncedWithBackend]) + + useEffect(() => { + let timeout: NodeJS.Timeout + if (initAnimation) { + timeout = setTimeout(() => setInitAnimation(false), 500) + } + return () => timeout && clearTimeout(timeout) + }, [initAnimation]) + + return ( + <div className="absolute left-0 right-0 top-0"> + {isSyncedWithBackend ? ( + <div className="h-0.5 w-full rounded-full bg-primary-200" /> + ) : ( + <motion.div + initial={{ scaleX: 0 }} + animate={{ scaleX: 1 }} + transition={{ duration: saveDelay / 1000 }} + className="h-0.5 w-full origin-left rounded-full bg-primary-200" + /> + )} + </div> + ) +} diff --git a/src/components/form/question/Assistance.tsx b/src/components/form/question/Assistance.tsx index c61554426..6a0d55ae6 100644 --- a/src/components/form/question/Assistance.tsx +++ b/src/components/form/question/Assistance.tsx @@ -1,12 +1,18 @@ -import { useRule } from '@/publicodes-state' +import { useForm, useRule } from '@/publicodes-state' import Label from '@/components/form/question/Label' import NumberInput from '@/components/form/question/NumberInput' +import { + getBgCategoryColor, + getBorderCategoryColor, +} from '@/helpers/getCategoryColorClass' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useEffect, useRef } from 'react' +import { twMerge } from 'tailwind-merge' type Props = { - question: string - assistance: string + question: DottedName + assistance: DottedName setTempValue?: (value: number | undefined) => void } @@ -18,6 +24,8 @@ export default function Assistance({ const { setValue: setValueOfQuestion, value: valueOfQuestion } = useRule(question) + const { currentCategory } = useForm() + const { type, label, @@ -53,7 +61,12 @@ export default function Assistance({ ]) return ( - <div className="mb-4 w-[20rem] max-w-full rounded-xl bg-white p-4 sm:w-2/3"> + <div + className={twMerge( + 'mb-4 w-[20rem] max-w-full rounded-xl border-2 p-4 sm:w-2/3', + getBgCategoryColor(currentCategory, '50'), + getBorderCategoryColor(currentCategory, '200') + )}> <Label question={question} size="sm" diff --git a/src/components/form/question/BooleanInput.tsx b/src/components/form/question/BooleanInput.tsx index 118b4ba90..57f8f54e2 100644 --- a/src/components/form/question/BooleanInput.tsx +++ b/src/components/form/question/BooleanInput.tsx @@ -1,6 +1,6 @@ import ChoiceInput from '@/components/misc/ChoiceInput' import { useClientTranslation } from '@/hooks/useClientTranslation' -import { NodeValue } from '@/publicodes-state/types' +import { NodeValue } from '@incubateur-ademe/nosgestesclimat' type Props = { value: NodeValue @@ -22,7 +22,7 @@ export default function BooleanInput({ const { t } = useClientTranslation() return ( - <fieldset className="align flex flex-col items-end"> + <fieldset className="flex flex-col gap-2"> <legend className="sr-only">{label}</legend> <ChoiceInput diff --git a/src/components/form/question/Category.tsx b/src/components/form/question/Category.tsx new file mode 100644 index 000000000..e3802895e --- /dev/null +++ b/src/components/form/question/Category.tsx @@ -0,0 +1,25 @@ +import Emoji from '@/design-system/utils/Emoji' +import { getTextDarkColor } from '@/helpers/getCategoryColorClass' +import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { twMerge } from 'tailwind-merge' + +type Props = { + question: DottedName +} + +export default function Category({ question }: Props) { + const { category } = useRule(question) + const { icons, title } = useRule(category) + return ( + <div className="flex"> + <div + className={twMerge( + 'mb-1 text-xs font-bold leading-none lg:mb-2 lg:text-sm', + getTextDarkColor(category) + )}> + <Emoji>{icons}</Emoji> {title} + </div> + </div> + ) +} diff --git a/src/components/form/question/ChoicesInput.tsx b/src/components/form/question/ChoicesInput.tsx index de7232d38..07b4f2b48 100644 --- a/src/components/form/question/ChoicesInput.tsx +++ b/src/components/form/question/ChoicesInput.tsx @@ -1,7 +1,8 @@ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Choice from './choicesInput/Choice' type Props = { - question: string + question: DottedName value: string isMissing: boolean choices: any[] @@ -24,7 +25,7 @@ export default function ChoicesInput(props: Props) { } = props return ( - <fieldset className="align flex flex-col items-end"> + <fieldset className="flex flex-col gap-2"> <legend className="sr-only">{label}</legend> {choices && diff --git a/src/components/form/question/Label.tsx b/src/components/form/question/Label.tsx index 97edbd8c4..c7d5ce057 100644 --- a/src/components/form/question/Label.tsx +++ b/src/components/form/question/Label.tsx @@ -15,13 +15,14 @@ import Markdown from '@/design-system/utils/Markdown' import { useClientTranslation } from '@/hooks/useClientTranslation' import { QuestionSize } from '@/types/values' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { motion } from 'framer-motion' import { useState } from 'react' import { twMerge } from 'tailwind-merge' type Props = { - question: string + question: DottedName label?: string description?: string size?: QuestionSize @@ -31,7 +32,7 @@ type Props = { const sizeClassNames = { sm: 'mb-1 text-sm', - md: 'mb-3 text-lg md:text-xl', + md: 'mb-3 text-lg md:text-2xl', } export default function Label({ @@ -51,7 +52,7 @@ export default function Label({ <> <label className={twMerge( - `flex ${sizeClassNames[size]} font-semibold`, + `flex ${sizeClassNames[size]} gap-2 font-semibold`, className )} aria-label={label} @@ -60,7 +61,7 @@ export default function Label({ onClick={(e) => e.preventDefault()}> <h1 className={twMerge( - 'mb-0 inline text-sm sm:text-base md:text-lg [&_p]:mb-0', + 'mb-0 inline flex-1 text-lg md:text-xl [&_p]:mb-0', titleClassName )} tabIndex={0} @@ -79,23 +80,31 @@ export default function Label({ setIsOpen((previsOpen) => !previsOpen) }} color="secondary" - size="sm" - className={`mx-2 inline-block h-8 w-8 min-w-8 items-center justify-center rounded-full p-0 font-mono`} + size="xs" + className={`inline-flex h-6 w-6 items-center justify-center rounded-full p-0 align-text-bottom font-mono`} title={t("Voir plus d'informations")}> i </Button> ) : null} </label> - + {question === 'logement . âge' && ( + <div className="mb-6 mt-2 text-xs italic md:text-sm"> + Un petit doute ? L’info sera sûrement dans votre contrat d’assurance + logement. + </div> + )} {isOpen && description ? ( <motion.div initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-3 origin-top rounded-md bg-white p-2 text-sm"> - <Markdown>{description}</Markdown>{' '} + className="mb-3 origin-top rounded-xl border-2 border-primary-50 bg-gray-100 p-3 text-sm"> + <Markdown className="[&>blockquote]:mb-2 [&>blockquote]:mt-0 [&>blockquote]:p-0 [&>blockquote]:text-default [&>p]:mb-2"> + {description} + </Markdown>{' '} <Button - size="sm" + size="xs" + color={'secondary'} onClick={() => { trackEvent(questionCloseInfo({ question })) setIsOpen(false) diff --git a/src/components/form/question/Mosaic.tsx b/src/components/form/question/Mosaic.tsx index dfb2a41ec..cd0a9f428 100644 --- a/src/components/form/question/Mosaic.tsx +++ b/src/components/form/question/Mosaic.tsx @@ -1,8 +1,9 @@ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import MosaicQuestion from './mosaic/MosaicQuestion' type Props = { - question: string - questionsOfMosaic: string[] + question: DottedName + questionsOfMosaic: DottedName[] } export default function Mosaic({ @@ -11,7 +12,7 @@ export default function Mosaic({ ...props }: Props) { return ( - <fieldset className="grid gap-4 md:grid-cols-2"> + <fieldset className="grid gap-2 md:grid-cols-2 md:gap-4"> {questionsOfMosaic ? questionsOfMosaic.map((questionOfMosaic, index) => ( <MosaicQuestion diff --git a/src/components/form/question/Notification.tsx b/src/components/form/question/Notification.tsx index 648a42a0b..d6232abf2 100644 --- a/src/components/form/question/Notification.tsx +++ b/src/components/form/question/Notification.tsx @@ -1,13 +1,19 @@ -import Trans from '@/components/translation/Trans' -import Button from '@/design-system/inputs/Button' import Markdown from '@/design-system/utils/Markdown' -import { useRule } from '@/publicodes-state' +import { + getBgCategoryColor, + getBorderCategoryColor, + getTextCategoryColor, +} from '@/helpers/getCategoryColorClass' +import { useForm, useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { motion } from 'framer-motion' type Props = { - notification: string + notification: DottedName } export default function Notification({ notification }: Props) { - const { description, setValue } = useRule(notification) + const { description } = useRule(notification) + + const { currentCategory } = useForm() if (!description) return @@ -16,11 +22,8 @@ export default function Notification({ notification }: Props) { initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-4 flex flex-col items-end rounded-md bg-white p-4 text-sm"> - <Markdown className="notification">{description}</Markdown> - <Button size="sm" onClick={() => setValue(false)}> - <Trans>J'ai compris</Trans> - </Button> + className={`mb-4 flex flex-col items-end rounded-xl border-2 ${getBorderCategoryColor(currentCategory, '200')} ${getBgCategoryColor(currentCategory, '100')} !${getTextCategoryColor(currentCategory, '700')} p-4 pb-0 text-sm`}> + <Markdown className="notification pb-0">{description}</Markdown> </motion.div> ) } diff --git a/src/components/form/question/NumberInput.tsx b/src/components/form/question/NumberInput.tsx index 4fd187736..2bd6565f8 100644 --- a/src/components/form/question/NumberInput.tsx +++ b/src/components/form/question/NumberInput.tsx @@ -1,7 +1,7 @@ import Trans from '@/components/translation/Trans' -import { useLocale } from '@/hooks/useLocale' +import { debounce } from '@/utils/debounce' import { HTMLAttributes } from 'react' -import { DebounceInput } from 'react-debounce-input' +import { NumberFormatValues, NumericFormat } from 'react-number-format' import { twMerge } from 'tailwind-merge' type Props = { @@ -12,6 +12,7 @@ type Props = { min?: number id?: string className?: string + defaultValue?: string | number | null | undefined } export default function NumberInput({ @@ -19,40 +20,32 @@ export default function NumberInput({ value = '', isMissing, setValue, - min = 0, className, id, ...props }: HTMLAttributes<HTMLInputElement> & Props) { - const locale = useLocale() + const handleValueChange = debounce((values: NumberFormatValues) => { + if (values.value === '') { + setValue(undefined) + } else { + setValue(Number(values.value)) + } + }, 300) return ( - <div className={twMerge(`flex items-center justify-end gap-1`, className)}> - <DebounceInput - debounceTimeout={300} - className={`focus:ring-primary max-w-[8rem] rounded-xl border-2 border-gray-200 bg-white p-2 text-right transition-colors focus:border-primary-700 focus:ring-2 md:max-w-full`} - type="number" - inputMode="numeric" - min={min} + <div + className={twMerge(`flex items-center justify-start gap-1`, className)}> + <NumericFormat value={isMissing ? '' : value} - placeholder={ - isMissing - ? value?.toLocaleString(locale, { - maximumFractionDigits: 1, - }) ?? '0' - : '0' - } - onChange={(event) => { - const inputValue = (event.target as HTMLInputElement).value - if (inputValue === '') { - setValue(undefined) - } else { - setValue(Number(inputValue)) - } - }} + placeholder={value?.toString() ?? '0'} + className={`focus:ring-primary max-w-[8rem] rounded-xl border-2 border-primary-200 bg-white p-2 text-right transition-colors focus:border-primary-700 focus:ring-2 md:max-w-full`} + thousandSeparator={' '} + allowNegative={false} + onValueChange={handleValueChange} id={id} {...props} /> + {unit ? ( <span className="whitespace-nowrap">   diff --git a/src/components/form/question/Suggestions.tsx b/src/components/form/question/Suggestions.tsx index 347571ed2..a31e19a98 100644 --- a/src/components/form/question/Suggestions.tsx +++ b/src/components/form/question/Suggestions.tsx @@ -1,30 +1,49 @@ 'use client' import { questionClickSuggestion } from '@/constants/tracking/question' -import Button from '@/design-system/inputs/Button' +import { baseClassNames, sizeClassNames } from '@/design-system/inputs/Button' import Emoji from '@/design-system/utils/Emoji' -import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { + getBgCategoryColor, + getBorderCategoryColor, + getHoverBgCategoryColor, + getHoverBorderCategoryColor, + getTextCategoryColor, +} from '@/helpers/getCategoryColorClass' +import { useForm, useRule } from '@/publicodes-state' import { capitalizeString } from '@/utils/capitalizeString' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat' +import { twMerge } from 'tailwind-merge' type Props = { question: DottedName - setValue: (value: number) => void + setValue: (value: NodeValue | Record<string, NodeValue>) => void } export default function Suggestions({ question, setValue }: Props) { const { suggestions } = useRule(question) + const { currentCategory } = useForm() + if (!suggestions?.length) return + return ( - <div className="mb-4 flex flex-wrap justify-end gap-2 text-sm"> + <div className="mb-6 flex flex-wrap justify-start gap-x-2 gap-y-2.5 text-sm"> {suggestions.map((suggestion) => ( - <Button + <button key={suggestion.label} data-cypress-id="suggestion" - size="xs" - className="text-xs font-normal md:text-sm" + className={twMerge( + 'text-xs font-medium transition-colors md:text-sm', + baseClassNames, + sizeClassNames.sm, + getBgCategoryColor(currentCategory, '200'), + getBorderCategoryColor(currentCategory, '200'), + getTextCategoryColor(currentCategory, '900'), + getHoverBgCategoryColor(currentCategory, '300'), + getHoverBorderCategoryColor(currentCategory, '300') + )} onClick={() => { trackEvent( questionClickSuggestion({ question, answer: suggestion.label }) @@ -34,7 +53,7 @@ export default function Suggestions({ question, setValue }: Props) { <Emoji className="flex items-center gap-1 leading-none"> {capitalizeString(suggestion.label)} </Emoji> - </Button> + </button> ))} </div> ) diff --git a/src/components/form/question/Warning.tsx b/src/components/form/question/Warning.tsx index 584d2d2b8..aac9c9fbd 100644 --- a/src/components/form/question/Warning.tsx +++ b/src/components/form/question/Warning.tsx @@ -20,11 +20,11 @@ export default function Warning({ type, plancher, warning, tempValue }: Props) { initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-4 flex flex-col items-end rounded-md bg-red-200 p-4 pb-0 text-sm"> + className="mb-4 inline-flex flex-auto flex-col items-start rounded-xl border-2 border-red-300 bg-red-200 p-4 pb-0 text-sm"> {warning ? ( <Markdown>{warning}</Markdown> ) : ( - <p> + <p className="p-0"> <Trans>La valeur minimum pour ce champ est de</Trans> {plancher} </p> )} diff --git a/src/components/form/question/choicesInput/Choice.tsx b/src/components/form/question/choicesInput/Choice.tsx index fefac7aea..990a28517 100644 --- a/src/components/form/question/choicesInput/Choice.tsx +++ b/src/components/form/question/choicesInput/Choice.tsx @@ -1,9 +1,10 @@ import ChoiceInput from '@/components/misc/ChoiceInput' import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { - question: string + question: DottedName choice: string active: boolean setValue: any @@ -18,15 +19,22 @@ export default function Choice({ id, ...props }: Props) { - const { title, description, icons } = useRule(question + ' . ' + choice) + const { title, description, icons } = useRule( + (question + ' . ' + choice) as DottedName + ) return ( <ChoiceInput label={ - <Emoji className="inline-flex items-center"> - <span className="mr-2">{title}</span> - {icons} - </Emoji> + <> + <Emoji className="inline-flex">{title}</Emoji> + {icons ? ( + <> + {' '} + <Emoji className="inline-flex items-center">{icons}</Emoji> + </> + ) : null} + </> } description={description} active={active} diff --git a/src/components/form/question/mosaic/MosaicQuestion.tsx b/src/components/form/question/mosaic/MosaicQuestion.tsx index 22271f18a..95701f543 100644 --- a/src/components/form/question/mosaic/MosaicQuestion.tsx +++ b/src/components/form/question/mosaic/MosaicQuestion.tsx @@ -5,8 +5,8 @@ import { questionTypeAnswer, } from '@/constants/tracking/question' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import MosaicBooleanInput from './mosaicQuestion/MosaicBooleanInput' import MosaicNumberInput from './mosaicQuestion/MosaicNumberInput' diff --git a/src/components/form/question/mosaic/mosaicQuestion/MosaicBooleanInput.tsx b/src/components/form/question/mosaic/mosaicQuestion/MosaicBooleanInput.tsx index c1fbbb02e..ae5469147 100644 --- a/src/components/form/question/mosaic/mosaicQuestion/MosaicBooleanInput.tsx +++ b/src/components/form/question/mosaic/mosaicQuestion/MosaicBooleanInput.tsx @@ -2,10 +2,11 @@ import Trans from '@/components/translation/Trans' import { DEFAULT_FOCUS_ELEMENT_ID } from '@/constants/accessibility' import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { motion } from 'framer-motion' type Props = { - question: string + question: DottedName title?: string icons?: string description?: string @@ -14,20 +15,21 @@ type Props = { } const buttonClassNames = { - inactive: 'border-gray-200 bg-gray-100 text-gray-400 cursor-default border-2', + inactive: + 'border-primary-200 bg-primary-100 text-primary-400 cursor-default border-2', checked: 'border-primary-700 text-primary-700 border-2', - unchecked: 'border-gray-200 bg-white hover:bg-primary-50 border-2', + unchecked: 'border-primary-200 hover:bg-primary-50 border-2', } const checkClassNames = { - inactive: 'border-gray-200', + inactive: 'border-primary-200', checked: 'border-primary-700', - unchecked: 'border-gray-200', + unchecked: 'border-primary-200', } const labelClassNames = { - inactive: 'text-gray-500', + inactive: 'text-primary-500', checked: 'text-primary-700', - unchecked: 'text-gray-700', + unchecked: 'text-primary-700', } export default function MosaicBooleanInput({ @@ -47,55 +49,59 @@ export default function MosaicBooleanInput({ ? 'checked' : 'unchecked' return ( - <label - className={`relative flex cursor-pointer items-center gap-2 rounded-xl border px-4 py-2 text-left transition-colors ${buttonClassNames[status]}`} - htmlFor={`${DEFAULT_FOCUS_ELEMENT_ID}-${index}`}> - <input - type="checkbox" - disabled={isInactive} - className="absolute h-[1px] w-[1px] opacity-0" - onClick={() => { - setValue(value ? 'non' : 'oui') - }} - data-cypress-id={`${question}-${value}`} - id={`${DEFAULT_FOCUS_ELEMENT_ID}-${index}`} - {...props} - /> + <div className="flex md:block"> + <label + className={`relative flex h-full cursor-pointer items-center gap-2 rounded-xl border bg-white px-4 py-2 text-left transition-colors ${buttonClassNames[status]}`} + htmlFor={`${DEFAULT_FOCUS_ELEMENT_ID}-${index}`}> + <input + type="checkbox" + disabled={isInactive} + className="absolute h-[1px] w-[1px] opacity-0" + onClick={() => { + setValue(value ? 'non' : 'oui') + }} + data-cypress-id={`${question}-${value}`} + id={`${DEFAULT_FOCUS_ELEMENT_ID}-${index}`} + {...props} + /> - <span - className={`${checkClassNames[status]} flex h-5 w-5 items-center justify-center rounded-sm border-2 leading-4`}> - {status === 'checked' ? ( - <motion.div - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - transition={{ duration: 0.2 }} - className={`font-mono text-2xl ${labelClassNames[status]}`}> - ✓ - </motion.div> - ) : ( - '' - )} - </span> + <span + className={`${checkClassNames[status]} flex h-5 w-5 items-center justify-center rounded-sm border-2 leading-4`}> + {status === 'checked' ? ( + <motion.div + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + transition={{ duration: 0.2 }} + className={`font-mono text-2xl ${labelClassNames[status]}`}> + ✓ + </motion.div> + ) : ( + '' + )} + </span> - <div className="flex-1"> - {title && icons ? ( - <span - className={`inline align-middle text-sm font-medium md:text-xl ${labelClassNames[status]}`}> - <Emoji className="inline-flex">{title}</Emoji>{' '} - <Emoji className="inline-flex items-center">{icons}</Emoji> - </span> - ) : null} - {description ? ( - <p className="mb-0 text-xs italic md:text-sm"> - {description.split('\n')[0]} - </p> - ) : null} - </div> - {isInactive ? ( - <div className="absolute bottom-1 right-4 top-1 flex -rotate-12 items-center justify-center rounded-xl border-2 border-black bg-white p-2 text-xs font-semibold text-black"> - <Trans>Bientôt disponible</Trans> + <div className="flex-1"> + {title && icons ? ( + <span + className={`inline-block align-middle text-sm md:text-lg ${labelClassNames[status]}`}> + <Emoji className=""> + {title} + {icons ? <> {icons}</> : null} + </Emoji> + </span> + ) : null} + {description ? ( + <p className="mb-0 text-xs italic md:text-sm"> + {description.split('\n')[0]} + </p> + ) : null} </div> - ) : null} - </label> + {isInactive ? ( + <div className="absolute bottom-1 right-4 top-1 flex -rotate-12 items-center justify-center rounded-xl border-2 border-black bg-white p-2 text-xs font-semibold text-black"> + <Trans>Bientôt disponible</Trans> + </div> + ) : null} + </label> + </div> ) } diff --git a/src/components/form/question/mosaic/mosaicQuestion/MosaicNumberInput.tsx b/src/components/form/question/mosaic/mosaicQuestion/MosaicNumberInput.tsx index c33461482..d89dca756 100644 --- a/src/components/form/question/mosaic/mosaicQuestion/MosaicNumberInput.tsx +++ b/src/components/form/question/mosaic/mosaicQuestion/MosaicNumberInput.tsx @@ -2,8 +2,9 @@ import { DEFAULT_FOCUS_ELEMENT_ID } from '@/constants/accessibility' import Button from '@/design-system/inputs/Button' import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { - question: string + question: DottedName title?: string icons?: string description?: string @@ -28,19 +29,19 @@ export default function NumberInput({ return ( <div className={ - 'flex items-center justify-between gap-4 rounded-xl border-2 border-gray-200 bg-white px-2 py-2 md:py-4' + 'flex items-center justify-between gap-4 rounded-xl border-2 border-primary-200 bg-white p-2 py-3' }> <div> {title && icons ? ( - <span className="text-sm font-semibold md:text-xl"> - <Emoji className="inline-flex items-center"> + <span className="mb-1 block text-sm font-medium md:text-xl"> + <Emoji className="inline-flex items-center leading-tight"> {title} {icons} </Emoji> </span> ) : null} {description ? ( <> - <p className="mb-0 text-xs italic md:text-sm"> + <p className="mb-0 text-xs italic md:text-xs"> {description.split('\n')[0]} </p> </> @@ -51,11 +52,11 @@ export default function NumberInput({ disabled={value === 0 || isMissing} onClick={() => setValue(Number(value) - 1)} size="sm" - className="z-10 h-8 w-8 items-center justify-center p-0 md:h-10 md:w-10"> + className="z-10 h-8 w-8 items-center justify-center p-0 md:h-8 md:w-8"> <span className="mb-[1px] block">-</span> </Button> <input - className="bg-transparent-100 w-10 text-center" + className="w-8 text-center" type="number" inputMode="numeric" value={isMissing ? '' : Number(value)} @@ -68,7 +69,7 @@ export default function NumberInput({ <Button onClick={() => setValue(isMissing ? 1 : Number(value) + 1)} size="sm" - className="z-10 h-8 w-8 items-center justify-center p-0 md:h-10 md:w-10"> + className="z-10 h-8 w-8 items-center justify-center p-0 md:h-8 md:w-8"> <span className="mb-[1px] block">+</span> </Button> </div> diff --git a/src/components/icons/ChevronLeft.tsx b/src/components/icons/ChevronLeft.tsx new file mode 100644 index 000000000..8234fd3fb --- /dev/null +++ b/src/components/icons/ChevronLeft.tsx @@ -0,0 +1,23 @@ +type Props = { + className?: string +} + +export default function ChevronLeft({ className }: Props) { + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className}> + <path + d="M15 18L9 12L15 6" + className="stroke-primary-700" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + ) +} diff --git a/src/design-system/icons/ChevronRight.tsx b/src/components/icons/ChevronRight.tsx similarity index 100% rename from src/design-system/icons/ChevronRight.tsx rename to src/components/icons/ChevronRight.tsx diff --git a/src/design-system/icons/ExternalLinkIcon.tsx b/src/components/icons/ExternalLinkIcon.tsx similarity index 100% rename from src/design-system/icons/ExternalLinkIcon.tsx rename to src/components/icons/ExternalLinkIcon.tsx diff --git a/src/components/icons/ListIcon.tsx b/src/components/icons/ListIcon.tsx new file mode 100644 index 000000000..42bfc20d1 --- /dev/null +++ b/src/components/icons/ListIcon.tsx @@ -0,0 +1,25 @@ +import { twMerge } from 'tailwind-merge' + +export default function ListIcon({ + className, + ...props +}: { + className?: string +}) { + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={twMerge('inline-block fill-default stroke-[1.5]', className)} + {...props}> + <path + fillRule="evenodd" + clipRule="evenodd" + d="M2 6C2 4.89543 2.89543 4 4 4C5.10457 4 6 4.89543 6 6C6 7.10457 5.10457 8 4 8C2.89543 8 2 7.10457 2 6ZM8 6C8 5.44772 8.44772 5 9 5H21C21.5523 5 22 5.44772 22 6C22 6.55229 21.5523 7 21 7L9 7C8.44772 7 8 6.55228 8 6ZM2 12C2 10.8954 2.89543 10 4 10C5.10457 10 6 10.8954 6 12C6 13.1046 5.10457 14 4 14C2.89543 14 2 13.1046 2 12ZM8 12C8 11.4477 8.44772 11 9 11L21 11C21.5523 11 22 11.4477 22 12C22 12.5523 21.5523 13 21 13L9 13C8.44772 13 8 12.5523 8 12ZM2 18C2 16.8954 2.89543 16 4 16C5.10457 16 6 16.8954 6 18C6 19.1046 5.10457 20 4 20C2.89543 20 2 19.1046 2 18ZM8 18C8 17.4477 8.44772 17 9 17L21 17C21.5523 17 22 17.4477 22 18C22 18.5523 21.5523 19 21 19L9 19C8.44772 19 8 18.5523 8 18Z" + /> + </svg> + ) +} diff --git a/src/components/icons/SaveCheckIcon.tsx b/src/components/icons/SaveCheckIcon.tsx new file mode 100644 index 000000000..1504e57f7 --- /dev/null +++ b/src/components/icons/SaveCheckIcon.tsx @@ -0,0 +1,26 @@ +import { twMerge } from 'tailwind-merge' + +export default function SaveCheckIcon({ + className, + ...props +}: { + className?: string +}) { + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={twMerge('inline-block fill-default stroke-[1.5]', className)} + {...props}> + <path d="M19.92,6.13c-.08-.31-.2-.6-.36-.87-.19-.3-.44-.56-.73-.85-.02,0-.04-.03-.06-.05l-3.06-3.07-.07-.06s-.04-.04-.05-.06c-.29-.29-.55-.54-.85-.73-.27-.16-.56-.28-.87-.36-.35-.08-.71-.08-1.11-.08h-7.83c-.44.01-.84.02-1.18.04-.56.05-1.08.15-1.57.4-.75.38-1.36.99-1.74,1.74C.19,2.67.09,3.19.04,3.75c-.04.54-.04,1.2-.04,2.01v8.48c0,.81,0,1.47.04,2.01.05.56.15,1.08.4,1.57.38.75.99,1.36,1.74,1.74.49.25,1.01.35,1.57.4.34.02.74.03,1.18.04h7.77c-.16-.34-.25-.71-.25-1.1,0-.31.06-.62.17-.9h-6.62v-5.4c0-.3,0-.46.01-.58h.01c.12-.02.28-.02.58-.02h6.8c.3,0,.46,0,.58.01h.01c.01.13.01.29.01.59v3.98c.64-.25,1.37-.24,2,.05v-4.06c0-.25,0-.5-.02-.71-.02-.23-.06-.5-.2-.77-.19-.37-.5-.68-.87-.87-.27-.14-.54-.18-.77-.2-.21-.02-.46-.02-.71-.02h-6.86c-.25,0-.5,0-.71.02-.23.02-.5.06-.77.2-.37.19-.68.5-.87.87-.14.27-.18.54-.2.77-.02.21-.02.46-.02.71v5.4s-.06,0-.09,0c-.44-.03-.66-.1-.82-.18-.37-.19-.68-.5-.87-.87-.08-.16-.15-.38-.18-.82-.04-.45-.04-1.03-.04-1.89V5.8c0-.86,0-1.44.04-1.89.03-.44.1-.66.18-.82.19-.37.5-.68.87-.87.16-.08.38-.15.82-.18.03,0,.06,0,.09,0v2.4c0,.25,0,.5.02.71.02.23.06.5.2.77.19.37.5.68.87.87.27.14.54.18.77.2.21.02.46.02.71.02h6.86c.25,0,.5,0,.71-.02.23-.02.5-.06.77-.2.37-.19.68-.5.87-.87.14-.27.18-.54.2-.77.02-.21.02-.46.02-.71v-.02l1.36,1.36c.38.38.45.46.49.54.06.09.1.18.12.28.02.09.03.2.03.74v6.87c0,.86,0,1.44-.04,1.89-.01.11-.02.21-.03.29l1.75-1.75c.1-.1.21-.19.32-.27v-7.12c0-.4,0-.76-.08-1.11ZM14,4.4c0,.3,0,.46-.01.58h-.01c-.12.02-.28.02-.58.02h-6.8c-.3,0-.46,0-.58-.01h-.01c-.01-.13-.01-.29-.01-.59v-2.4h6.67c.54,0,.65.01.73.03.11.02.2.06.29.12.06.03.12.08.31.27v1.98Z" /> + <path d="M22.16,17.11l-4.5,4.5c-.39.39-1.03.39-1.42,0l-2-2c-.39-.39-.39-1.03,0-1.42s1.03-.39,1.42,0l1.29,1.3,2.98-2.99.81-.81c.39-.39,1.03-.39,1.42,0,.39.39.39,1.03,0,1.42Z" /> + <path + className="fill-green-500" + d="M22.16,15.69c.39.39.39,1.02,0,1.41l-4.5,4.5c-.39.39-1.02.39-1.41,0l-2-2c-.39-.39-.39-1.02,0-1.41.39-.39,1.02-.39,1.41,0l1.29,1.29,3.79-3.79c.39-.39,1.02-.39,1.41,0Z" + /> + </svg> + ) +} diff --git a/src/components/iframe/IframeDataShareModal.tsx b/src/components/iframe/IframeDataShareModal.tsx index 62e4ac698..743319122 100644 --- a/src/components/iframe/IframeDataShareModal.tsx +++ b/src/components/iframe/IframeDataShareModal.tsx @@ -24,7 +24,9 @@ export default function IframeDataShareModal() { const data = Object.keys(categories).reduce( (accumulator, categoryName) => ({ ...accumulator, - [categoryName.charAt(0)]: Math.round(categories[categoryName]), + [categoryName.charAt(0)]: Math.round( + categories[categoryName as keyof typeof categories] + ), }), {} ) diff --git a/src/components/layout/ContentLarge.tsx b/src/components/layout/ContentLarge.tsx new file mode 100644 index 000000000..13a42fd39 --- /dev/null +++ b/src/components/layout/ContentLarge.tsx @@ -0,0 +1,21 @@ +import Main from '@/design-system/layout/Main' +import { PropsWithChildren } from 'react' +import { twMerge } from 'tailwind-merge' + +type Props = { + className?: string +} +export default function ContentLarge({ + children, + className, +}: PropsWithChildren<Props>) { + return ( + <Main + className={twMerge( + 'flex w-full max-w-6xl flex-1 flex-col overflow-visible px-4 pt-4 lg:mx-auto', + className + )}> + {children} + </Main> + ) +} diff --git a/src/components/layout/ContentNarrow.tsx b/src/components/layout/ContentNarrow.tsx new file mode 100644 index 000000000..13e7920f5 --- /dev/null +++ b/src/components/layout/ContentNarrow.tsx @@ -0,0 +1,10 @@ +import Main from '@/design-system/layout/Main' +import { PropsWithChildren } from 'react' + +export default function ContentNarrow({ children }: PropsWithChildren) { + return ( + <Main className="mb-8 w-full max-w-4xl overflow-visible px-4 lg:mx-auto"> + {children} + </Main> + ) +} diff --git a/src/components/layout/FilAriane.tsx b/src/components/layout/FilAriane.tsx index aa419123d..8fdb30a52 100644 --- a/src/components/layout/FilAriane.tsx +++ b/src/components/layout/FilAriane.tsx @@ -1,15 +1,15 @@ 'use client' -import useFetchOrganisation from '@/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useFetchOrganisation' import Breadcrumbs from '@/design-system/layout/Breadcrumbs' import { getOrganisationItems } from '@/helpers/filAriane/getOrganisationItems' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useFetchPollData } from '@/hooks/organisations/useFetchPollData' import { useUser } from '@/publicodes-state' import { useParams, usePathname } from 'next/navigation' const TARGETED_PATHS = ['/organisations'] -export default function FilAriane() { +export default function FilAriane({ className }: { className?: string }) { const pathname = usePathname() const params = useParams() @@ -50,5 +50,5 @@ export default function FilAriane() { return [] } - return <Breadcrumbs items={getBreadcrumbsItems()} /> + return <Breadcrumbs className={className} items={getBreadcrumbsItems()} /> } diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 50e2eeb3f..a7c69397d 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -19,7 +19,9 @@ import { } from '@/constants/tracking/layout' import InlineLink from '@/design-system/inputs/InlineLink' import Separator from '@/design-system/layout/Separator' +import { useLocale } from '@/hooks/useLocale' import { trackEvent } from '@/utils/matomo/trackEvent' +import { usePathname } from 'next/navigation' import { twMerge } from 'tailwind-merge' import Link from '../Link' import Logo from '../misc/Logo' @@ -27,19 +29,21 @@ import LanguageSwitchButton from '../translation/LanguageSwitchButton' import Trans from '../translation/Trans' export default function Footer({ className = '' }) { + const pathname = usePathname() + const locale = useLocale() + + const isHomePage = pathname === '/' || pathname === `/${locale}` return ( <footer className={twMerge( 'relative flex flex-col items-center gap-4 bg-gray-100 p-4 !pb-32 sm:p-8 md:mb-0', - className + className, + isHomePage ? 'bg-white' : '' )}> <div className="flex w-full items-start gap-12 md:max-w-5xl"> - <Logo - className="hiddenscale-75 lg:block" - onClick={() => trackEvent(footerClickLogo)} - /> + <Logo onClick={() => trackEvent(footerClickLogo)} /> - <div className="w-full flex-1 "> + <div className="flex-1"> <div className="flex flex-col flex-wrap justify-start gap-x-5 gap-y-2 pt-4 sm:flex-row md:items-center"> <InlineLink href="/a-propos" diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 628be4348..dfdebc511 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,30 +1,19 @@ 'use client' -import { usePathname } from 'next/navigation' import HeaderDesktop from './header/HeaderDesktop' import HeaderMobile from './header/HeaderMobile' -export default function Header() { - const pathname = usePathname() - - const shouldHideMostOfContent = - pathname.includes('/simulateur') || pathname.includes('/tutoriel') - - const shouldHideSomeOfContent = pathname.includes('/fin') - +type Props = { + isSticky?: boolean +} +export default function Header({ isSticky = true }: Props) { return ( <> {/* Displayed only on mobile (screens < 768px) */} - <HeaderMobile - shouldHideMostOfContent={shouldHideMostOfContent} - shouldHideSomeOfContent={shouldHideSomeOfContent} - /> + <HeaderMobile isSticky={isSticky} /> {/* Displayed only on desktop */} - <HeaderDesktop - shouldHideMostOfContent={shouldHideMostOfContent} - shouldHideSomeOfContent={shouldHideSomeOfContent} - /> + <HeaderDesktop isSticky={isSticky} /> </> ) } diff --git a/src/components/layout/header/HeaderDesktop.tsx b/src/components/layout/header/HeaderDesktop.tsx index 060f09299..76a9f8866 100644 --- a/src/components/layout/header/HeaderDesktop.tsx +++ b/src/components/layout/header/HeaderDesktop.tsx @@ -28,13 +28,9 @@ import CTAButton from './headerDesktop/CTAButton' import DebugIndicator from './headerDesktop/DebugIndicator' type Props = { - shouldHideMostOfContent: boolean - shouldHideSomeOfContent: boolean + isSticky: boolean } -export default function HeaderDesktop({ - shouldHideMostOfContent, - shouldHideSomeOfContent, -}: Props) { +export default function HeaderDesktop({ isSticky }: Props) { const { t } = useClientTranslation() const pathname = usePathname() @@ -43,16 +39,14 @@ export default function HeaderDesktop({ const { getLinkToSimulateurPage } = useSimulateurPage() - const canBeSticky = !shouldHideMostOfContent && !shouldHideSomeOfContent - return ( <header className={twMerge( - 'hidden h-20 items-center lg:block', - canBeSticky ? 'sticky top-0 z-50' : '' + 'mb-8 hidden h-20 items-center lg:block', + isSticky ? 'sticky top-0 z-50' : '' )}> <div className="absolute bottom-0 left-0 right-0 top-0 flex h-20 w-full items-center border-b bg-white shadow-sm"> - <div className="mx-auto flex h-full w-full max-w-6xl justify-between gap-6"> + <div className="mx-auto flex h-full w-full max-w-6xl justify-between gap-6 px-4"> <div className="flex items-center justify-between gap-20"> <div className="flex origin-left items-center justify-center"> <Logo onClick={() => trackEvent(headerClickLogo)} /> diff --git a/src/components/layout/header/HeaderMobile.tsx b/src/components/layout/header/HeaderMobile.tsx index 86945e5e1..ca6becc77 100644 --- a/src/components/layout/header/HeaderMobile.tsx +++ b/src/components/layout/header/HeaderMobile.tsx @@ -1,41 +1,31 @@ 'use client' import Logo from '@/components/misc/Logo' -import Trans from '@/components/translation/Trans' -import ButtonLink from '@/design-system/inputs/ButtonLink' import { useIframe } from '@/hooks/useIframe' import { twMerge } from 'tailwind-merge' import BottomMenu from './headerMobile/BottomMenu' import FoldableMenu from './headerMobile/FoldableMenu' type Props = { - shouldHideMostOfContent: boolean - shouldHideSomeOfContent: boolean + isSticky: boolean } -export default function HeaderMobile({ - shouldHideMostOfContent, - shouldHideSomeOfContent, -}: Props) { +export default function HeaderMobile({ isSticky }: Props) { const { isIframeOnlySimulation } = useIframe() - const canBeSticky = !shouldHideMostOfContent && !shouldHideSomeOfContent return ( <header className={twMerge( 'flex justify-between bg-white p-4 shadow-sm lg:hidden', - canBeSticky ? 'sticky top-0 z-50' : '' + isSticky ? 'sticky top-0 z-50' : '' )}> <Logo /> - {!shouldHideMostOfContent && <FoldableMenu />} - - {shouldHideMostOfContent && !isIframeOnlySimulation ? ( - <ButtonLink href="/" size="sm" color="text"> - ← <Trans>Revenir à l'accueil</Trans> - </ButtonLink> - ) : null} - - {!shouldHideMostOfContent && <BottomMenu />} + {!isIframeOnlySimulation && ( + <> + <FoldableMenu /> + <BottomMenu /> + </> + )} </header> ) } diff --git a/src/components/layout/header/_components/OrganisationLink.tsx b/src/components/layout/header/_components/OrganisationLink.tsx index a3d34905e..6dfbdbaff 100644 --- a/src/components/layout/header/_components/OrganisationLink.tsx +++ b/src/components/layout/header/_components/OrganisationLink.tsx @@ -1,9 +1,9 @@ 'use client' -import useFetchOrganisation from '@/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useFetchOrganisation' import OrganisationIcon from '@/components/icons/OrganisationIcon' import Trans from '@/components/translation/Trans' import { headerClickOrganisation } from '@/constants/tracking/layout' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useUser } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' diff --git a/src/components/messages/ToastDisplay.tsx b/src/components/messages/ToastDisplay.tsx new file mode 100644 index 000000000..90586e9dd --- /dev/null +++ b/src/components/messages/ToastDisplay.tsx @@ -0,0 +1,33 @@ +import { marianne } from '@/app/layout' +import { ToastContainer } from 'react-toastify' +import 'react-toastify/dist/ReactToastify.css' +import { twMerge } from 'tailwind-merge' + +type Props = { + position?: + | 'top-right' + | 'top-center' + | 'top-left' + | 'bottom-right' + | 'bottom-center' + | 'bottom-left' + autoClose?: number + hideProgressBar?: boolean + closeOnClick?: boolean + pauseOnHover?: boolean + draggable?: boolean + progress?: number + progressClassName?: string + className?: string +} + +export default function ToastDisplay(props: Props) { + return ( + <ToastContainer + className={twMerge(props?.className ?? '', marianne.className)} + aria-live="polite" + position="bottom-right" + {...props} + /> + ) +} diff --git a/src/components/misc/ChoiceInput.tsx b/src/components/misc/ChoiceInput.tsx index 146c6a288..89f617111 100644 --- a/src/components/misc/ChoiceInput.tsx +++ b/src/components/misc/ChoiceInput.tsx @@ -11,6 +11,19 @@ type Props = { 'data-cypress-id'?: string } +const buttonClassNames = { + checked: 'border-primary-700 text-primary-700', + unchecked: 'border-primary-200 hover:bg-primary-50', +} +const checkClassNames = { + checked: 'border-primary-700 before:bg-primary-700', + unchecked: 'border-primary-300', +} +const labelClassNames = { + checked: 'text-primary-700', + unchecked: 'text-gray-700', +} + export default function ChoiceInput({ label, description, @@ -21,15 +34,14 @@ export default function ChoiceInput({ ...props }: HTMLAttributes<HTMLInputElement> & PropsWithChildren<Props>) { const [isOpen, setIsOpen] = useState(false) + + const status = active ? 'checked' : 'unchecked' + return ( <> - <div className="mb-2 flex flex-1 items-center gap-1 sm:gap-2"> + <div className="flex items-center gap-2"> <label - className={`flex cursor-pointer items-center gap-[2px] rounded-xl border-2 border-gray-200 px-1 py-2 text-right text-xs sm:gap-2 sm:px-4 sm:text-sm md:text-xl ${ - active - ? 'border-2 border-primary-700 bg-white' - : 'bg-white text-default hover:bg-primary-50' - } transition-colors`} + className={`relative flex cursor-pointer items-center gap-2 rounded-xl border-2 bg-white px-4 py-2 text-left transition-colors ${buttonClassNames[status]}`} data-cypress-id={`${props['data-cypress-id']}-label`}> <input type="radio" @@ -39,13 +51,12 @@ export default function ChoiceInput({ {...props} /> <span - className={`${ - active - ? 'border-primary-700 before:bg-primary-700 ' - : 'border-gray-300 before:bg-white' - } relative flex h-4 w-4 min-w-4 items-center justify-center rounded-full border-2 text-sm before:absolute before:left-0.5 before:top-0.5 before:h-2 before:w-2 before:rounded-full before:p-1 md:h-5 md:w-5 md:text-base md:before:h-3 md:before:w-3`} + className={`${checkClassNames[status]} relative flex h-5 w-5 items-center justify-center rounded-full border-2 text-sm before:absolute before:left-0.5 before:top-0.5 before:h-3 before:w-3 before:rounded-full before:p-1 md:h-5 md:w-5 md:text-base md:before:h-3 md:before:w-3`} /> - {label ?? children} + <span + className={`inline flex-1 align-middle text-sm md:text-lg ${labelClassNames[status]}`}> + {label ?? children} + </span> </label> {description ? ( <QuestionButton @@ -54,7 +65,7 @@ export default function ChoiceInput({ ) : null} </div> {description && isOpen ? ( - <div className="mb-4 w-auto rounded-lg bg-white p-2 text-xs sm:max-w-[30rem] sm:text-right sm:text-sm md:rounded-full"> + <div className="mb-4 w-auto rounded-xl border-2 border-primary-50 bg-white p-3 text-sm sm:max-w-[30rem]"> <Markdown className="!mb-0 !inline">{description}</Markdown> </div> ) : null} diff --git a/src/components/misc/ChoicesValue.tsx b/src/components/misc/ChoicesValue.tsx index 270078341..092e1134a 100644 --- a/src/components/misc/ChoicesValue.tsx +++ b/src/components/misc/ChoicesValue.tsx @@ -1,12 +1,12 @@ import { useRule } from '@/publicodes-state' -import { NodeValue } from '@/publicodes-state/types' +import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat' type Props = { value: NodeValue - question: string + question: DottedName } export default function ChoicesValue({ value, question }: Props) { - const { title, icons } = useRule(question + ' . ' + value) + const { title, icons } = useRule((question + ' . ' + value) as DottedName) return ( <> diff --git a/src/components/misc/Logo.tsx b/src/components/misc/Logo.tsx index 3ec1d33ab..e0c895d3f 100644 --- a/src/components/misc/Logo.tsx +++ b/src/components/misc/Logo.tsx @@ -5,11 +5,24 @@ import Image from 'next/image' import { twMerge } from 'tailwind-merge' import Link from '../Link' +const imageClassSize = { + sm: 'w-[38px]', + md: 'w-[50px]', +} +const textClassSize = { + sm: 'ml-1 text-sm', + md: 'ml-2 text-lg', +} type Props = { onClick?: () => void className?: string + size?: 'sm' | 'md' } -export default function Logo({ onClick = () => null, className }: Props) { +export default function Logo({ + onClick = () => null, + className, + size = 'md', +}: Props) { const { isIframeOnlySimulation } = useIframe() return ( @@ -28,16 +41,23 @@ export default function Logo({ onClick = () => null, className }: Props) { alt="Logo Nos Gestes Climat" width="200" height="200" - className={'h-auto w-[50px]'} + className={twMerge('h-auto', imageClassSize[size])} /> <div - className={ - 'ml-2 origin-left text-lg font-extrabold uppercase !leading-[0.85] text-default transition-all duration-500 lg:block' - }> - <span className="block w-full !leading-[0.85]">Nos</span> - <span className="block w-full !leading-[0.85]">Gestes</span> - <span className="block w-full !leading-[0.85]">Climat</span> + className={twMerge( + 'origin-left font-extrabold uppercase !leading-[0.85] text-default transition-all duration-500 lg:block', + textClassSize[size] + )}> + <span className="block w-full whitespace-normal !leading-[0.85]"> + Nos + </span> + <span className="block w-full whitespace-normal !leading-[0.85]"> + Gestes + </span> + <span className="block w-full whitespace-normal !leading-[0.85]"> + Climat + </span> </div> </Link> </div> diff --git a/src/components/misc/QuestionButton.tsx b/src/components/misc/QuestionButton.tsx index 6ab054ff9..e4d61b160 100644 --- a/src/components/misc/QuestionButton.tsx +++ b/src/components/misc/QuestionButton.tsx @@ -1,4 +1,4 @@ -import { twMerge } from 'tailwind-merge' +import Button from '@/design-system/inputs/Button' type Props = { onClick: any @@ -11,22 +11,15 @@ export const colorClassNames = { primary: 'border-primary-700 text-primary-700', white: 'border-white text-white', } -export default function QuestionButton({ - onClick, - color = 'primary', - title, - className, -}: Props) { +export default function QuestionButton({ onClick, title }: Props) { return ( - <button + <Button onClick={onClick} title={title} - className={twMerge( - 'z-10 h-6 w-6 min-w-6 rounded-full border-2 bg-transparent text-sm font-bold leading-none md:h-7 md:w-7 md:text-lg md:leading-none', - colorClassNames[color], - className - )}> - ? - </button> + color={'text'} + size="xs" + className="z-10 h-6 w-6 p-0 font-normal"> + (?) + </Button> ) } diff --git a/src/components/misc/ValueChangeDisplay.tsx b/src/components/misc/ValueChangeDisplay.tsx index 9265c772d..aea9c2133 100644 --- a/src/components/misc/ValueChangeDisplay.tsx +++ b/src/components/misc/ValueChangeDisplay.tsx @@ -1,44 +1,79 @@ 'use client' +import { formatFootprint } from '@/helpers/formatters/formatFootprint' +import { useClientTranslation } from '@/hooks/useClientTranslation' import { useLocale } from '@/hooks/useLocale' -import { useRule } from '@/publicodes-state' +import { useForm, useRule } from '@/publicodes-state' +import { usePathname } from 'next/navigation' import { useEffect, useRef, useState } from 'react' +import { twMerge } from 'tailwind-merge' +import Trans from '../translation/Trans' -export default function ValueChangeDisplay() { +export default function ValueChangeDisplay({ + className, +}: { + className?: string +}) { + const { t } = useClientTranslation() const locale = useLocale() + + const pathname = usePathname() + + const { currentQuestion } = useForm() + const { numericValue } = useRule('bilan') const prevValue = useRef(numericValue) - const [displayDifference, setDisplayDifference] = useState('') + const [displayDifference, setDisplayDifference] = useState(0) - const [shouldDisplay, setShouldDisplay] = useState(false) + const prevQuestion = useRef(currentQuestion) useEffect(() => { - const difference = numericValue - prevValue.current + if (prevQuestion.current !== currentQuestion) { + setDisplayDifference(0) + } + }, [currentQuestion]) - setDisplayDifference( - `${difference > 0 ? '+' : '-'} ${Math.abs(difference).toLocaleString( - locale, - { - maximumFractionDigits: 1, - } - )}` - ) + useEffect(() => { + const difference = numericValue - prevValue.current - setShouldDisplay(difference !== 0) + setDisplayDifference(difference) prevValue.current = numericValue - - const timer = setTimeout(() => setShouldDisplay(false), 3000) - return () => clearTimeout(timer) }, [numericValue, locale]) - if (!shouldDisplay) return + const isNegative = displayDifference < 0 + + const { formattedValue, unit } = formatFootprint(displayDifference, { + locale, + t, + }) + + if (displayDifference === 0 || !pathname.includes('simulateur/bilan')) { + return null + } + return ( - <div className="animate-valuechange" key={numericValue}> - <strong className="text-lg">{displayDifference}</strong>{' '} - <span className="text-xs font-light"> - kgCO<sub>2</sub>e + <div + className={twMerge( + '-z-0 whitespace-nowrap', + isNegative + ? 'animate-valuechange-reverse text-green-700' + : 'animate-valuechange text-red-700', + className + )} + key={numericValue} + aria-label={t('{{signe}} {{value}} {{unit}} sur votre empreinte', { + signe: isNegative ? t('moins') : t('plus'), + value: formattedValue, + unit, + })}> + <strong className="text-base font-black"> + {displayDifference > 0 ? '+' : '-'} + {formattedValue} + </strong>{' '} + <span className="text-xs"> + {unit} <Trans>sur votre empreinte</Trans> </span> </div> ) diff --git a/src/components/nextjs/ClientContainer.tsx b/src/components/nextjs/ClientContainer.tsx deleted file mode 100644 index 0c6ebc337..000000000 --- a/src/components/nextjs/ClientContainer.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client' - -import { PropsWithChildren } from 'react' - -/** - * Used to wrap server components added into client components - * @returns - */ -export default function ClientContainer({ children }: PropsWithChildren) { - return <>{children}</> -} diff --git a/src/components/organisations/ExportDataButton.tsx b/src/components/organisations/ExportDataButton.tsx index b5f8fca54..59ceedb92 100644 --- a/src/components/organisations/ExportDataButton.tsx +++ b/src/components/organisations/ExportDataButton.tsx @@ -25,6 +25,8 @@ export default function ExportDataButton({ }: ButtonProps & Props) { const [isLoading, setIsLoading] = useState(false) + if (simulationRecaps?.length < 3) return null + function handleClick() { if (onClick) { onClick() diff --git a/src/components/organisations/orgaStatistics/DetailedStatistics.tsx b/src/components/organisations/orgaStatistics/DetailedStatistics.tsx index 3cc42a74d..342db34f9 100644 --- a/src/components/organisations/orgaStatistics/DetailedStatistics.tsx +++ b/src/components/organisations/orgaStatistics/DetailedStatistics.tsx @@ -1,12 +1,12 @@ 'use client' +import ChevronRight from '@/components/icons/ChevronRight' import Trans from '@/components/translation/Trans' import { organisationsDashboardClickFunFacts } from '@/constants/tracking/pages/organisationsDashboard' -import ChevronRight from '@/design-system/icons/ChevronRight' import Button from '@/design-system/inputs/Button' -import { DottedName } from '@/publicodes-state/types' +import { Entries } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' -import { FunFacts } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, FunFacts } from '@incubateur-ademe/nosgestesclimat' import importedFunFacts from '@incubateur-ademe/nosgestesclimat/public/funFactsRules.json' import { utils } from 'publicodes' import { useState } from 'react' @@ -34,10 +34,18 @@ type Props = { export default function DetailedStatistics({ funFacts }: Props) { const [isSectionVisible, setIsSectionVisible] = useState(false) - const funFactsByCategory: Record<DottedName, [string, DottedName][]> = {} + const funFactsByCategory = {} as Record< + DottedName, + [keyof Partial<FunFacts>, DottedName | undefined][] + > - Object.entries(plusFunFactsRules).forEach((item) => { - const parent = utils.ruleParent(item[1]) as keyof typeof funFactsByCategory + const plusFunFactsRulesEntries = Object.entries(plusFunFactsRules) as Entries< + typeof plusFunFactsRules + > + + plusFunFactsRulesEntries.forEach((item) => { + if (!item[1]) return + const parent = utils.ruleParent(item[1]) as DottedName funFactsByCategory[parent] = [...(funFactsByCategory[parent] || []), item] }) diff --git a/src/components/organisations/orgaStatistics/FunFactsBlock.tsx b/src/components/organisations/orgaStatistics/FunFactsBlock.tsx index fa3d84432..9188cd4d4 100644 --- a/src/components/organisations/orgaStatistics/FunFactsBlock.tsx +++ b/src/components/organisations/orgaStatistics/FunFactsBlock.tsx @@ -1,7 +1,6 @@ 'use client' -import { DottedName } from '@/publicodes-state/types' -import { FunFacts } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, FunFacts } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' import FunFactsItem from './funFacts/FunFactsItem' diff --git a/src/components/organisations/orgaStatistics/StatisticsBlocks.tsx b/src/components/organisations/orgaStatistics/StatisticsBlocks.tsx index 56ea0a26f..a5e0e34ea 100644 --- a/src/components/organisations/orgaStatistics/StatisticsBlocks.tsx +++ b/src/components/organisations/orgaStatistics/StatisticsBlocks.tsx @@ -2,6 +2,7 @@ import VerticalBarChart from '@/components/charts/VerticalBarChart' import Trans from '@/components/translation/Trans' import { carboneMetric } from '@/constants/metric' import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' +import { Entries } from '@/publicodes-state/types' import { SimulationRecap } from '@/types/organisations' import CategoryChartItem from './statisticsBlocks/CategoryChartItem' import ResultsSoonBanner from './statisticsBlocks/ResultsSoonBanner' @@ -49,7 +50,7 @@ function formatSimulationRecaps(simulationRecaps: SimulationRecap[]) { 'services sociétaux': 0, } ) - Object.keys(result).forEach((key: string) => { + Object.keys(result).forEach((key) => { result[key as keyof typeof result] = result[key as keyof typeof result] / simulationRecaps.length }) @@ -114,7 +115,7 @@ export default function StatisticsBlocks({ <div className="col-span-1 min-h-[212px] rounded-xl bg-gray-100 py-4"> <VerticalBarChart className={`mt-0 h-[calc(100%-48px)]`}> - {Object.entries(result) + {(Object.entries(result) as Entries<typeof result>) .filter(([key]) => key !== 'bilan') .map(([key, value], index) => ( <CategoryChartItem diff --git a/src/components/organisations/orgaStatistics/funFacts/DetailedFunFacts.tsx b/src/components/organisations/orgaStatistics/funFacts/DetailedFunFacts.tsx index 1c4d48f8d..b06a40162 100644 --- a/src/components/organisations/orgaStatistics/funFacts/DetailedFunFacts.tsx +++ b/src/components/organisations/orgaStatistics/funFacts/DetailedFunFacts.tsx @@ -2,9 +2,9 @@ import SaveIcon from '@/components/icons/SaveIcon' import Trans from '@/components/translation/Trans' import { organisationsDashboardClickFunFactsDownload } from '@/constants/tracking/pages/organisationsDashboard' import Button from '@/design-system/inputs/Button' -import { DottedName } from '@/publicodes-state/types' +import { Entries } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' -import { FunFacts } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, FunFacts } from '@incubateur-ademe/nosgestesclimat' import { toPng } from 'html-to-image' import { useParams } from 'next/navigation' import { utils } from 'publicodes' @@ -23,47 +23,52 @@ export default function DetailedFunFacts({ }: Props) { const params = useParams() - const funFactsByCategory: Record<DottedName, [string, DottedName][]> = - useMemo(() => { - const localFunFactsByCategory: Record< - DottedName, - [string, DottedName][] - > = {} - Object.entries(plusFunFactsRules).forEach((item) => { - const parent = utils.ruleParent( - item[1] - ) as keyof typeof localFunFactsByCategory - localFunFactsByCategory[parent] = [ - ...(localFunFactsByCategory[parent] || []), - item, - ] - }) + const funFactsByCategory = useMemo(() => { + const localFunFactsByCategory = {} as Record< + DottedName, + [keyof Partial<FunFacts>, DottedName | undefined][] + > - return localFunFactsByCategory - }, [plusFunFactsRules]) + const plusFunFactsRulesEntries = Object.entries( + plusFunFactsRules + ) as Entries<typeof plusFunFactsRules> + + plusFunFactsRulesEntries.forEach((item) => { + if (!item[1]) return + const parent = utils.ruleParent(item[1]) as DottedName + localFunFactsByCategory[parent] = [ + ...(localFunFactsByCategory[parent] || []), + item, + ] + }) + + return localFunFactsByCategory + }, [plusFunFactsRules]) return ( <div className="rounded-xl bg-gray-100"> <div className="bg-gray-100 p-8 pb-16" id="funFactsPlus"> <h2>Chiffres clés</h2> - {Object.entries(funFactsByCategory).map( - ([category, funFactsEntries]) => ( - <div key={category} className="mt-12"> - <FunFactsPlusCategoryTitle category={category} /> - <div className="mt-8 grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3"> - {funFactsEntries.map(([funFactKey, dottedName]) => ( - <FunFactsItem - key={funFactKey} - funFactKey={funFactKey} - dottedName={dottedName} - funFacts={funFacts} - small={true} - /> - ))} - </div> + {( + Object.entries(funFactsByCategory) as Entries< + typeof funFactsByCategory + > + ).map(([category, funFactsEntries]) => ( + <div key={category} className="mt-12"> + <FunFactsPlusCategoryTitle category={category} /> + <div className="mt-8 grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3"> + {funFactsEntries.map(([funFactKey, dottedName]) => ( + <FunFactsItem + key={funFactKey} + funFactKey={funFactKey} + dottedName={dottedName as DottedName} + funFacts={funFacts} + small={true} + /> + ))} </div> - ) - )} + </div> + ))} </div> <div className="mb-8 px-8"> diff --git a/src/components/organisations/orgaStatistics/funFacts/FunFactsItem.tsx b/src/components/organisations/orgaStatistics/funFacts/FunFactsItem.tsx index 8f09fb248..6e9b99e64 100644 --- a/src/components/organisations/orgaStatistics/funFacts/FunFactsItem.tsx +++ b/src/components/organisations/orgaStatistics/funFacts/FunFactsItem.tsx @@ -1,7 +1,6 @@ import Emoji from '@/design-system/utils/Emoji' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' -import { FunFacts } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, FunFacts } from '@incubateur-ademe/nosgestesclimat' import { twMerge } from 'tailwind-merge' type Props = { diff --git a/src/components/organisations/orgaStatistics/funFacts/FunFactsPlusCategoryTitle.tsx b/src/components/organisations/orgaStatistics/funFacts/FunFactsPlusCategoryTitle.tsx index 7697d2dc3..4061e4924 100644 --- a/src/components/organisations/orgaStatistics/funFacts/FunFactsPlusCategoryTitle.tsx +++ b/src/components/organisations/orgaStatistics/funFacts/FunFactsPlusCategoryTitle.tsx @@ -1,6 +1,6 @@ import Title from '@/design-system/layout/Title' import { useRule } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' export default function FunFactsPlusCategoryTitle({ category, diff --git a/src/components/organisations/orgaStatistics/statisticsBlocks/CategoryChartItem.tsx b/src/components/organisations/orgaStatistics/statisticsBlocks/CategoryChartItem.tsx index 10c374c8a..491eadd8d 100644 --- a/src/components/organisations/orgaStatistics/statisticsBlocks/CategoryChartItem.tsx +++ b/src/components/organisations/orgaStatistics/statisticsBlocks/CategoryChartItem.tsx @@ -5,6 +5,7 @@ import { getBackgroundColor } from '@/helpers/getCategoryColorClass' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' import { capitalizeString } from '@/utils/capitalizeString' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { Tooltip } from 'react-tooltip' export default function CategoryChartItem({ @@ -13,7 +14,7 @@ export default function CategoryChartItem({ value, index, }: { - category: string + category: DottedName value: number maxValue: number index: number diff --git a/src/components/providers/Providers.tsx b/src/components/providers/Providers.tsx index 6c6c686a8..34c1c9d18 100644 --- a/src/components/providers/Providers.tsx +++ b/src/components/providers/Providers.tsx @@ -32,13 +32,14 @@ export default function Providers({ // or the landing page for organisations const shouldAlwaysDisplayChildren = NO_MODEL_PATHNAME_EXCEPTIONS.includes(pathname) + if (shouldAlwaysDisplayChildren && isLoading) { return children } if (isLoading) { return ( - <div className="flex flex-1 items-center justify-center"> + <div className="flex h-screen flex-1 items-center justify-center"> <Loader color="dark" /> </div> ) diff --git a/src/components/providers/providers/SimulationSyncProvider.tsx b/src/components/providers/providers/SimulationSyncProvider.tsx index 968a03542..2483cade0 100644 --- a/src/components/providers/providers/SimulationSyncProvider.tsx +++ b/src/components/providers/providers/SimulationSyncProvider.tsx @@ -56,8 +56,7 @@ export default function SimulationSyncProvider({ // If the simulation is unfinished, is not in a group, poll, or is not already saved via email, we do not save it const shouldSyncWithBackend = useMemo<boolean>(() => { - // Fix to avoid computedResults bilan === 0 bug - if (progression !== 1) return false + if (!isInitialized) return false if (computedResults[defaultMetric].bilan === 0) { // Send an error to Sentry @@ -69,7 +68,7 @@ export default function SimulationSyncProvider({ return false } - if (groups?.length || polls?.length) { + if ((groups?.length || polls?.length) && progression === 1) { return true } @@ -78,7 +77,15 @@ export default function SimulationSyncProvider({ } return false - }, [progression, user.email, groups, polls, savedViaEmail, computedResults]) + }, [ + progression, + user.email, + groups, + polls, + savedViaEmail, + computedResults, + isInitialized, + ]) const isSyncedWithBackend = timeoutRef.current || isPending ? false : true diff --git a/src/components/results/categoriesAccordion/AccordionItemWithRule.tsx b/src/components/results/categoriesAccordion/AccordionItemWithRule.tsx index 464042bc2..62dedd713 100644 --- a/src/components/results/categoriesAccordion/AccordionItemWithRule.tsx +++ b/src/components/results/categoriesAccordion/AccordionItemWithRule.tsx @@ -11,10 +11,11 @@ import { getBackgroundColor } from '@/helpers/getCategoryColorClass' import { useRule, useSimulation } from '@/publicodes-state' import { Metric } from '@/publicodes-state/types' import { trackEvent } from '@/utils/matomo/trackEvent' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import SubcategoriesList from './accordionItemWithRule/SubcategoriesList' type Props = { - dottedName: string + dottedName: DottedName maxValue: number index?: number metric?: Metric @@ -43,6 +44,7 @@ export default function AccordionItemWithRule({ title={title} icons={icons} barColor={getBackgroundColor(dottedName)} + shouldDisplayValue={false} displayValue={ <span> <strong className="font-black">{formattedValue}</strong>{' '} diff --git a/src/components/results/categoriesAccordion/accordionItemWithRule/SubcategoriesList.tsx b/src/components/results/categoriesAccordion/accordionItemWithRule/SubcategoriesList.tsx index d0c03bb38..6c1a8c0bb 100644 --- a/src/components/results/categoriesAccordion/accordionItemWithRule/SubcategoriesList.tsx +++ b/src/components/results/categoriesAccordion/accordionItemWithRule/SubcategoriesList.tsx @@ -1,6 +1,7 @@ import { defaultMetric } from '@/constants/metric' import { useEngine, useRule } from '@/publicodes-state' -import { DottedName, Metric } from '@/publicodes-state/types' +import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import SubcategoryListItem from './subcategoriesList/SubcategoryListItem' type Props = { @@ -16,8 +17,8 @@ export default function SubcategoriesList({ const { getNumericValue, checkIfValid } = useEngine({ metric }) const sortedSubcategories = subcategories[category] - ?.filter((subcategory: string) => checkIfValid(subcategory)) - .sort((categoryA: string, categoryB: string) => { + ?.filter((subcategory) => checkIfValid(subcategory)) + .sort((categoryA, categoryB) => { const valueA = getNumericValue(categoryA) ?? 0 const valueB = getNumericValue(categoryB) ?? 0 @@ -28,7 +29,7 @@ export default function SubcategoriesList({ return ( <ul> - {sortedSubcategories.map((name: string) => ( + {sortedSubcategories.map((name) => ( <SubcategoryListItem key={name} subcategory={name} diff --git a/src/components/results/categoriesAccordion/accordionItemWithRule/subcategoriesList/SubcategoryListItem.tsx b/src/components/results/categoriesAccordion/accordionItemWithRule/subcategoriesList/SubcategoryListItem.tsx index d6d45d2cc..1353fa8c9 100644 --- a/src/components/results/categoriesAccordion/accordionItemWithRule/subcategoriesList/SubcategoryListItem.tsx +++ b/src/components/results/categoriesAccordion/accordionItemWithRule/subcategoriesList/SubcategoryListItem.tsx @@ -4,9 +4,10 @@ import Emoji from '@/design-system/utils/Emoji' import { formatFootprint } from '@/helpers/formatters/formatFootprint' import { useRule } from '@/publicodes-state' import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { - subcategory: string + subcategory: DottedName categoryValue: number metric?: Metric } diff --git a/src/components/results/categoriesChart/CategoryChartItem.tsx b/src/components/results/categoriesChart/CategoryChartItem.tsx index f10090519..59c2e61a8 100644 --- a/src/components/results/categoriesChart/CategoryChartItem.tsx +++ b/src/components/results/categoriesChart/CategoryChartItem.tsx @@ -4,6 +4,7 @@ import VerticalBarChartItem from '@/components/charts/verticalBarChart/VerticalB import { getBackgroundColor } from '@/helpers/getCategoryColorClass' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { formatValue } from 'publicodes' export default function CategoryChartItem({ @@ -11,7 +12,7 @@ export default function CategoryChartItem({ maxValue, index, }: { - category: string + category: DottedName maxValue: number index: number }) { diff --git a/src/components/specialQuestions/Avion.tsx b/src/components/specialQuestions/Avion.tsx index 90b7769b4..836e456e1 100644 --- a/src/components/specialQuestions/Avion.tsx +++ b/src/components/specialQuestions/Avion.tsx @@ -1,19 +1,20 @@ import Question from '@/components/form/Question' import Trans from '@/components/translation/Trans' import Button from '@/design-system/inputs/Button' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useState } from 'react' import PencilIcon from '../icons/PencilIcon' import ThreeYearsInput from './avion/ThreeYearsInput' type Props = { - question: string + question: DottedName } export default function Avion({ question, ...props }: Props) { const [isOpen, setIsOpen] = useState(false) return ( <> - <Question question={question} {...props} /> - <div className="mb-4 flex flex-col items-end"> + <Question question={question} className="mb-4" {...props} /> + <div className="mb-4 flex flex-col items-start"> <Button color="link" size="xs" diff --git a/src/components/specialQuestions/Voiture.tsx b/src/components/specialQuestions/Voiture.tsx index 98927d86e..5210e5c72 100644 --- a/src/components/specialQuestions/Voiture.tsx +++ b/src/components/specialQuestions/Voiture.tsx @@ -1,19 +1,20 @@ import Question from '@/components/form/Question' import Trans from '@/components/translation/Trans' import Button from '@/design-system/inputs/Button' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useState } from 'react' import PencilIcon from '../icons/PencilIcon' import JourneysInput from './voiture/JourneysInput' type Props = { - question: string + question: DottedName } export default function Voiture({ question, ...props }: Props) { const [isOpen, setIsOpen] = useState(false) return ( <> - <Question question={question} {...props} /> - <div className="mb-4 flex flex-col items-end"> + <Question question={question} className="mb-4" {...props} /> + <div className="mb-4 flex flex-col items-start"> <Button color="link" size="xs" diff --git a/src/components/specialQuestions/avion/ThreeYearsInput.tsx b/src/components/specialQuestions/avion/ThreeYearsInput.tsx index ec31fcbcd..2157631f1 100644 --- a/src/components/specialQuestions/avion/ThreeYearsInput.tsx +++ b/src/components/specialQuestions/avion/ThreeYearsInput.tsx @@ -3,11 +3,12 @@ import NumberInput from '@/components/form/question/NumberInput' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useLocale } from '@/hooks/useLocale' import { useRule } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { motion } from 'framer-motion' import { useEffect, useMemo, useRef, useState } from 'react' type Props = { - question: string + question: DottedName setTempValue?: (value: number | undefined) => void } @@ -68,7 +69,7 @@ export default function ThreeYearsInput({ question, setTempValue }: Props) { initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-2 rounded-xl bg-white p-4"> + className="mb-2 rounded-xl border-2 border-primary-50 bg-white p-4"> <Label question={question} size="sm" label={String(currentYear)} /> <NumberInput unit={unit} diff --git a/src/components/specialQuestions/voiture/JourneysInput.tsx b/src/components/specialQuestions/voiture/JourneysInput.tsx index db9367ccf..8c0b4f329 100644 --- a/src/components/specialQuestions/voiture/JourneysInput.tsx +++ b/src/components/specialQuestions/voiture/JourneysInput.tsx @@ -3,11 +3,12 @@ import { Journey } from '@/types/journey' import { useEffect, useMemo, useRef, useState } from 'react' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { JourneysInputDesktop } from './journeysInput/JourneysInputDesktop' import JourneysInputMobile from './journeysInput/JourneysInputMobile' type Props = { - question: string + question: DottedName setTempValue?: (value: number | undefined) => void } diff --git a/src/components/specialQuestions/voiture/journeysInput/JourneysInputDesktop.tsx b/src/components/specialQuestions/voiture/journeysInput/JourneysInputDesktop.tsx index 9a88e6ed5..ca82ceadb 100644 --- a/src/components/specialQuestions/voiture/journeysInput/JourneysInputDesktop.tsx +++ b/src/components/specialQuestions/voiture/journeysInput/JourneysInputDesktop.tsx @@ -26,7 +26,7 @@ export function JourneysInputDesktop({ initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-2 hidden w-full overflow-scroll rounded-xl bg-white p-2 text-xs lg:block"> + className="mb-2 hidden w-full overflow-scroll rounded-xl border-2 border-primary-50 bg-white p-2 text-xs lg:block"> <table className="table w-full border-collapse"> <tbody className="block w-full"> <tr className="table-row w-full"> diff --git a/src/components/specialQuestions/voiture/journeysInput/JourneysInputMobile.tsx b/src/components/specialQuestions/voiture/journeysInput/JourneysInputMobile.tsx index 198ae9ccb..46d0ee45f 100644 --- a/src/components/specialQuestions/voiture/journeysInput/JourneysInputMobile.tsx +++ b/src/components/specialQuestions/voiture/journeysInput/JourneysInputMobile.tsx @@ -26,7 +26,7 @@ export default function JourneysInputMobile({ initial={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.2 }} - className="mb-2 block w-full overflow-scroll rounded-xl bg-white p-2 lg:hidden"> + className="mb-2 block w-full overflow-scroll rounded-xl border-2 border-primary-50 bg-white p-2 lg:hidden"> <table className="block w-full border-collapse"> <tbody className="block w-full"> <AddJourneyMobile diff --git a/src/components/total/Total.tsx b/src/components/total/Total.tsx index 3ec178dfd..89b6488e6 100644 --- a/src/components/total/Total.tsx +++ b/src/components/total/Total.tsx @@ -1,46 +1,39 @@ 'use client' import QuestionButton from '@/components/misc/QuestionButton' -import Trans from '@/components/translation/Trans' import { simulateurCloseScoreInfo, simulateurOpenScoreInfo, } from '@/constants/tracking/pages/simulateur' -import Emoji from '@/design-system/utils/Emoji' -import { formatCarbonFootprint } from '@/helpers/formatters/formatCarbonFootprint' +import { getBgCategoryColor } from '@/helpers/getCategoryColorClass' import { useClientTranslation } from '@/hooks/useClientTranslation' -import { - useCurrentSimulation, - useEngine, - useRule, - useUser, -} from '@/publicodes-state' +import { useCurrentSimulation, useForm, useUser } from '@/publicodes-state' import { trackEvent } from '@/utils/matomo/trackEvent' import { useEffect, useState } from 'react' -import Explanation from './_components/Explanation' -import ListToggle from './_components/ListToggle' -import Progress from './_components/Progress' +import { twMerge } from 'tailwind-merge' +import ValueChangeDisplay from '../misc/ValueChangeDisplay' +import ButtonBack from './total/ButtonBack' +import Explanation from './total/Explanation' +import Progress from './total/Progress' +import TotalButtons from './total/TotalButtons' +import TotalFootprintNumber from './total/TotalFootprintNumber' -type Props = { +export default function Total({ + toggleQuestionList, + toggleSaveModal, + simulationMode = true, +}: { toggleQuestionList?: () => void -} -export default function Total({ toggleQuestionList }: Props) { + toggleSaveModal?: () => void + simulationMode?: boolean +}) { const { t } = useClientTranslation() - const { numericValue } = useRule('bilan') - - const { getNumericValue } = useEngine() - const { tutorials, hideTutorial, showTutorial } = useUser() - const { actionChoices, progression } = useCurrentSimulation() + const { progression } = useCurrentSimulation() - const actionChoicesSumValue = Object.keys(actionChoices || {}).reduce( - (acc, key) => { - return acc + (actionChoices[key] ? getNumericValue(key) : 0) - }, - 0 - ) + const { currentCategory } = useForm() const [hasManuallyOpenedTutorial, setHasManuallyOpenedTutorial] = useState(false) @@ -55,7 +48,6 @@ export default function Total({ toggleQuestionList }: Props) { hideTutorial('scoreExplanation') } } - const carbonFootprintValue = numericValue - actionChoicesSumValue useEffect(() => { if ( @@ -73,43 +65,56 @@ export default function Total({ toggleQuestionList }: Props) { ]) return ( - <div className="md:mb-2"> - <div className="relative mb-2 flex items-center gap-4 overflow-hidden rounded-xl bg-primary-800 px-4 py-2 text-white md:justify-center md:text-center "> - <Progress /> + <header + className={twMerge( + 'fixed top-0 z-50 h-16 w-full md:bg-white', + getBgCategoryColor(currentCategory, '50'), + !simulationMode && 'static' + )}> + <div + className={twMerge( + 'relative flex h-full items-center gap-4 overflow-visible pb-3 pt-2 lg:pb-5 lg:pt-4', + !simulationMode && 'border-b border-primary-100' + )}> + {simulationMode && <Progress />} - <Emoji className="z-10 text-4xl md:text-6xl">🌍</Emoji> + <div className="mb-0 flex w-full max-w-6xl justify-between overflow-visible pl-1 pr-4 lg:mx-auto lg:px-4"> + <div className="relative flex items-center gap-1 lg:gap-4"> + {simulationMode && <ButtonBack onClick={toggleSaveModal} />} - <div className="flex gap-4"> - <div className="z-10"> - <span className="block text-2xl font-bold md:text-3xl"> - {numericValue !== carbonFootprintValue && ( - <span className="relative text-xl text-gray-300 md:text-2xl"> - <span className="absolute right-0 top-1/2 h-[2px] w-full -rotate-45 transform bg-gray-300" /> - {formatCarbonFootprint(numericValue).formattedValue} - </span> - )}{' '} - {formatCarbonFootprint(carbonFootprintValue).formattedValue}{' '} - <Trans>{formatCarbonFootprint(carbonFootprintValue).unit}</Trans> - </span> - <span className="block text-sm md:text-base"> - <Trans i18nKey="Total.unit"> - de CO<sub className="text-white">2</sub>e / an - </Trans> - </span> + <TotalFootprintNumber /> + + <QuestionButton + onClick={toggleOpen} + title={t('Comprendre mon score')} + /> + </div> + {toggleQuestionList ? ( + <TotalButtons + toggleQuestionList={toggleQuestionList} + toggleSaveModal={toggleSaveModal} + /> + ) : null} + </div> + + <div + className="absolute -bottom-7 left-10 w-full lg:left-4" + aria-live="polite"> + <div className="w-full max-w-6xl md:mx-auto"> + <ValueChangeDisplay /> </div> </div> - <QuestionButton - onClick={toggleOpen} - color="white" - title={t('Comprendre mon score')} - /> - {toggleQuestionList ? ( - <ListToggle toggleQuestionList={toggleQuestionList} /> - ) : null} </div> {!tutorials.scoreExplanation ? ( - <Explanation toggleOpen={toggleOpen} /> + <div className="relative mx-auto max-w-6xl"> + <Explanation + toggleOpen={toggleOpen} + isFirstToggle={ + !tutorials.scoreExplanation && !hasManuallyOpenedTutorial + } + /> + </div> ) : null} - </div> + </header> ) } diff --git a/src/components/total/_components/ListToggle.tsx b/src/components/total/_components/ListToggle.tsx deleted file mode 100644 index a47cd9c82..000000000 --- a/src/components/total/_components/ListToggle.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useClientTranslation } from '@/hooks/useClientTranslation' - -type Props = { - toggleQuestionList: () => void -} - -export default function ListToggle({ toggleQuestionList }: Props) { - const { t } = useClientTranslation() - - return ( - <button - onClick={toggleQuestionList} - className="absolute right-4 h-8 w-8 md:right-6" - title={t('Voir la liste des questions')}> - <svg - className="fill-white" - viewBox="0 0 32 32" - xmlns="http://www.w3.org/2000/svg"> - <path d="m30 8.178c0-1.65-1.35-3-3-3h-12.04c-1.65 0-3 1.35-3 3s1.35 3 3 3h12.04c1.65 0 3-1.35 3-3z"></path> - <path d="m22.04 13.178h-17.04c-1.65 0-3 1.35-3 3s1.35 3 3 3h17.04c1.65 0 3-1.35 3-3s-1.35-3-3-3z"></path> - <path d="m22.04 21.178h-17.04c-1.65 0-3 1.35-3 3s1.35 3 3 3h17.04c1.65 0 3-1.35 3-3s-1.35-3-3-3z"></path> - <path d="m4.966 11.531h.034c.293 0 .572-.129.762-.353l4.001-4.71c.358-.421.307-1.052-.114-1.409-.423-.359-1.054-.306-1.409.114l-3.185 3.748-1.25-1.694c-.329-.444-.954-.539-1.398-.211s-.539.954-.211 1.398l2 2.71c.182.246.465.396.771.405z"></path> - </svg> - </button> - ) -} diff --git a/src/components/total/_components/Planet.tsx b/src/components/total/_components/Planet.tsx deleted file mode 100644 index b90637bc2..000000000 --- a/src/components/total/_components/Planet.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { twMerge } from 'tailwind-merge' - -type Props = { - className?: string - width?: string - height?: string -} - -export default function Planet({ className, ...props }: Props) { - return ( - <svg - width="64" - height="64" - viewBox="0 0 64 64" - fill="none" - xmlns="http://www.w3.org/2000/svg" - className={twMerge('z-10', className)} - {...props}> - <path - d="M31 3C23.5739 3 16.452 5.94999 11.201 11.201C5.94999 16.452 3 23.5739 3 31C3 38.4261 5.94999 45.548 11.201 50.799C16.452 56.05 23.5739 59 31 59C38.4261 59 45.548 56.05 50.799 50.799C56.05 45.548 59 38.4261 59 31C59 23.5739 56.05 16.452 50.799 11.201C45.548 5.94999 38.4261 3 31 3Z" - fill="#85CBF8" - /> - <path - d="M31 3V59C38.4261 59 45.548 56.05 50.799 50.799C56.05 45.548 59 38.4261 59 31C59 23.5739 56.05 16.452 50.799 11.201C45.548 5.94999 38.4261 3 31 3Z" - fill="#7BBEEB" - /> - <path - d="M16.0001 31C16.0001 19.95 19.8801 10.06 26.0001 3.45996C19.5585 4.64223 13.735 8.04518 9.54275 13.0769C5.35049 18.1086 3.05469 24.4507 3.05469 31C3.05469 37.5492 5.35049 43.8913 9.54275 48.923C13.735 53.9547 19.5585 57.3577 26.0001 58.54C19.8801 51.94 16.0001 42.05 16.0001 31Z" - fill="#9FDDFF" - /> - <path - d="M46 31C46 42.05 42.12 51.94 36 58.54C42.4417 57.3577 48.2651 53.9547 52.4574 48.923C56.6497 43.8913 58.9455 37.5492 58.9455 31C58.9455 24.4507 56.6497 18.1086 52.4574 13.0769C48.2651 8.04518 42.4417 4.64223 36 3.45996C42.1 10.06 46 19.95 46 31Z" - fill="#75B8E5" - /> - <path - d="M58.6698 28.35C57.2728 29.8714 55.9925 31.496 54.8398 33.21C54.5423 34.635 54.5972 36.1111 54.9998 37.51C55.4576 38.1703 56.0026 38.7657 56.6198 39.28L58.9998 35L58.6698 28.35ZM57.8298 27.19C56.8055 27.0783 55.8515 26.6154 55.1298 25.88C55.0244 25.6878 54.9585 25.4763 54.9362 25.2582C54.9138 25.04 54.9355 24.8196 54.9998 24.61C55.2304 23.5606 55.746 22.5952 56.4898 21.82C56.5546 21.7373 56.6468 21.6806 56.7498 21.66L58.9998 25L57.8298 27.19ZM9.31982 13.51C11.5598 14.29 12.1498 17.09 12.7098 19.39C13.5798 22.92 16.9998 28 16.9998 28L17.9998 26C18.658 27.3393 19.4593 28.6033 20.3898 29.77C21.4061 31.039 22.626 32.1305 23.9998 33C26.3498 34.12 28.6498 34.28 30.9998 35.4C31.2764 35.501 31.5189 35.6777 31.6998 35.91C32.0898 36.52 31.5298 37.25 31.1498 37.91C30.741 38.6016 30.5422 39.3972 30.5778 40.1997C30.6133 41.0023 30.8815 41.7772 31.3498 42.43C32.3498 43.79 34.2098 44.54 34.6998 46.16C34.8484 46.8168 34.8654 47.4966 34.7498 48.16C34.3965 51.3533 33.9032 54.53 33.2698 57.69C33.2174 57.8594 33.2174 58.0406 33.2698 58.21C33.3998 58.51 33.8098 58.56 34.1398 58.51C35.0813 58.2415 35.9251 57.7068 36.5698 56.97C39.4698 54.21 42.3365 51.42 45.1698 48.6C46.0935 47.7952 46.8452 46.8123 47.3798 45.71C47.6015 44.9012 47.8892 44.1119 48.2398 43.35C48.5198 42.9 48.9198 42.53 49.1698 42.07C49.3212 41.8511 49.4023 41.5912 49.4023 41.325C49.4023 41.0588 49.3212 40.799 49.1698 40.58C48.9225 40.359 48.6172 40.2133 48.2898 40.16L42.8998 38.72C43.4098 37.43 42.1198 36.15 40.8998 35.53C38.4178 34.3148 35.5944 33.9866 32.8998 34.6C32.2577 34.8119 31.6353 35.0796 31.0398 35.4L29.3498 34.73C29.3498 34.73 29.4798 32.82 29.0698 32.51C28.6598 32.2 27.8398 32.42 27.6198 31.93C27.3998 31.44 28.1698 30.93 28.0198 30.37C27.8698 29.81 26.4998 30.37 26.0698 30.42C25.7532 30.4755 25.4276 30.4475 25.1251 30.3388C24.8226 30.2301 24.5537 30.0445 24.3447 29.8003C24.1358 29.556 23.9941 29.2615 23.9336 28.9459C23.8732 28.6302 23.896 28.3042 23.9998 28C24.3198 26.71 25.1898 26.43 26.4998 26.22C28.0598 25.96 29.9098 26.46 30.5698 27.9C30.5915 26.061 31.2647 24.2893 32.4698 22.9C33.6298 21.48 34.6098 21.1 36.1698 20.13C35.9898 20 38.5998 19.66 39.6898 19.27C40.2298 19.08 40.8998 18.63 40.6898 18.1C40.4798 17.57 39.5898 17.75 39.1798 18.2C38.7698 18.65 36.4498 18.26 35.9398 17.91C35.4298 17.56 35.7598 16.91 36.7498 16.91C37.2953 16.8988 37.8181 16.6898 38.221 16.3219C38.6239 15.954 38.8793 15.4522 38.9398 14.91C38.9398 14.53 37.9398 11.97 36.9398 11.97C36.1998 11.97 34.4998 13.5 33.8198 12.39C33.7399 12.1698 33.5853 11.9846 33.3828 11.8668C33.1804 11.7489 32.943 11.7059 32.7121 11.7452C32.4811 11.7845 32.2713 11.9036 32.1192 12.0817C31.9671 12.2599 31.8824 12.4858 31.8798 12.72C31.9796 13.9011 31.6216 15.0754 30.8798 16L25.8798 13.14C25.5548 12.992 25.2802 12.7522 25.0898 12.45C24.7298 11.73 25.4098 10.95 26.0898 10.45C27.6298 9.33 29.0898 8.16 30.5598 7.08C32.0298 6 33.5598 6.74 33.3298 9.08C36.6298 9.47 36.8398 8.08 35.9798 7.03C36.8798 7.36 39.7498 8.1 40.9798 11.03C41.1798 11.52 41.6498 15.25 43.7398 15.76C44.8998 16.04 45.5998 14.52 46.1298 13.76C47.3081 12.4824 48.8765 11.6314 50.5898 11.34L40.9998 5L32.9998 3L26.8098 3.56L25.3698 5.06L27.2298 5.38C27.2298 5.38 24.7498 6.69 24.3698 6.87C24.2798 6.87 24.0398 8.15 23.7698 8.15C21.7989 8.06522 19.8527 7.67734 17.9998 7L9.31982 13.51Z" - fill="#6BC47B" - /> - <path - d="M31 2C25.2644 2 19.6575 3.70082 14.8885 6.88738C10.1195 10.0739 6.40245 14.6031 4.20751 19.9022C2.01257 25.2012 1.43827 31.0322 2.55724 36.6576C3.67621 42.2831 6.43819 47.4504 10.4939 51.5061C14.5496 55.5618 19.7169 58.3238 25.3424 59.4428C30.9678 60.5617 36.7988 59.9874 42.0978 57.7925C47.3969 55.5976 51.9261 51.8806 55.1126 47.1115C58.2992 42.3425 60 36.7357 60 31C60 27.1917 59.2499 23.4206 57.7925 19.9022C56.3351 16.3837 54.199 13.1868 51.5061 10.4939C48.8132 7.801 45.6163 5.66488 42.0978 4.20749C38.5794 2.75011 34.8083 2 31 2ZM57.56 26.16C56.971 26.0022 56.4244 25.7152 55.96 25.32C55.9144 25.1565 55.9144 24.9835 55.96 24.82C56.1105 24.1616 56.3966 23.5417 56.8 23C57.1171 24.037 57.3709 25.0923 57.56 26.16ZM24.43 5.38C24.4617 5.4801 24.5089 5.57459 24.57 5.66L23.93 6C23.7514 6.1058 23.5992 6.25079 23.4849 6.42402C23.3706 6.59726 23.2971 6.79421 23.27 7L23.21 7.19C22.0353 7.09062 20.875 6.86259 19.75 6.51C21.2459 5.81808 22.8025 5.26573 24.4 4.86C24.3639 5.03294 24.3743 5.21236 24.43 5.38ZM17.37 7.71C17.4759 7.80932 17.6022 7.8844 17.74 7.93C19.6647 8.64655 21.6886 9.06144 23.74 9.16C24.63 9.16 24.93 8.26 25.14 7.59L27.7 6.27C27.8848 6.17235 28.0343 6.01929 28.1275 5.83231C28.2208 5.64533 28.2532 5.43382 28.2201 5.2275C28.187 5.02118 28.0901 4.83042 27.943 4.682C27.7959 4.53358 27.606 4.43496 27.4 4.4L27.54 4.26C31.362 3.75232 35.2484 4.07463 38.9345 5.20496C42.6206 6.33529 46.0197 8.24708 48.9 10.81C47.4846 11.2441 46.2266 12.0816 45.28 13.22C45.18 13.36 45.08 13.52 44.97 13.69C44.72 14.08 44.21 14.89 43.97 14.82C42.84 14.54 42.29 12.22 42.08 11.35C42.0308 11.1229 41.9673 10.8991 41.89 10.68C41.39 9.59579 40.6565 8.63553 39.7419 7.86795C38.8274 7.10037 37.7545 6.54445 36.6 6.24L36.35 6.15C36.1426 6.06242 35.9117 6.04739 35.6947 6.10733C35.4777 6.16728 35.2873 6.29869 35.1543 6.48031C35.0212 6.66193 34.9534 6.88313 34.9617 7.10811C34.97 7.33309 35.054 7.54868 35.2 7.72C35.35 7.91 35.41 8.07 35.43 8.04C35.0879 8.21436 34.6997 8.2773 34.32 8.22C34.2935 7.77137 34.1599 7.33564 33.9302 6.94936C33.7005 6.56308 33.3815 6.23752 33 6C32.5176 5.76571 31.9776 5.67598 31.4453 5.74163C30.913 5.80729 30.4111 6.02551 30 6.37C29.27 6.91 28.53 7.47 27.79 8.03C27.05 8.59 26.29 9.17 25.53 9.73C24.23 10.73 23.74 11.92 24.26 12.96C24.537 13.4124 24.9402 13.7739 25.42 14L30.42 16.86C30.6248 16.9779 30.8646 17.0194 31.0971 16.9773C31.3295 16.9352 31.5396 16.8122 31.69 16.63C32.5725 15.551 33.0217 14.1821 32.95 12.79L33.03 12.9C33.96 14.4 35.65 13.59 36.46 13.21L36.94 12.98C37.4046 13.5725 37.7448 14.2528 37.94 14.98C37.9021 15.2086 37.781 15.4152 37.6 15.56C37.3999 15.7729 37.1304 15.9076 36.84 15.94C36.4335 15.8691 36.015 15.9346 35.6497 16.1263C35.2843 16.318 34.9926 16.6252 34.82 17C34.7144 17.3085 34.715 17.6436 34.822 17.9517C34.9289 18.2598 35.1359 18.5232 35.41 18.7C35.6513 18.843 35.9101 18.9539 36.18 19.03C35.9117 19.0822 35.6627 19.2066 35.46 19.39L34.78 19.8C33.6289 20.4076 32.6093 21.2368 31.78 22.24C30.9138 23.2748 30.2765 24.4812 29.91 25.78C28.8309 25.2309 27.6033 25.045 26.41 25.25C25.41 25.42 23.61 25.72 23.11 27.78C22.9881 28.2589 22.9863 28.7606 23.105 29.2403C23.2236 29.7201 23.4589 30.1631 23.79 30.53C24.0942 30.8659 24.4781 31.1198 24.9063 31.2684C25.3344 31.4169 25.7931 31.4553 26.24 31.38L26.64 31.3C26.5471 31.6314 26.5719 31.9848 26.71 32.3C26.8499 32.5901 27.0663 32.8366 27.3359 33.0128C27.6056 33.189 27.9181 33.2883 28.24 33.3H28.32V33.38L27.79 33.23C26.6449 32.9505 25.5271 32.569 24.45 32.09C23.3119 31.3565 22.2989 30.4449 21.45 29.39L21.17 29.09C20.3036 27.9922 19.556 26.8056 18.94 25.55C18.8532 25.3773 18.7183 25.2334 18.5515 25.1358C18.3846 25.0382 18.1931 24.991 18 25C17.8153 25.0006 17.6344 25.0522 17.4773 25.1493C17.3203 25.2464 17.1931 25.3851 17.11 25.55L16.9 26C15.535 23.8776 14.4557 21.5845 13.69 19.18L13.62 18.89C13.13 16.89 12.52 14.46 10.76 13.19C12.6591 11.0247 14.8903 9.17491 17.37 7.71ZM4.00001 31C3.9961 25.1152 5.92492 19.392 9.49001 14.71C10.74 15.53 11.24 17.53 11.67 19.33L11.74 19.62C12.64 23.29 16.02 28.34 16.17 28.56C16.2677 28.7041 16.4014 28.8202 16.5577 28.8968C16.7141 28.9733 16.8877 29.0078 17.0615 28.9967C17.2352 28.9856 17.403 28.9293 17.5484 28.8334C17.6937 28.7375 17.8114 28.6054 17.89 28.45L18.04 28.16C18.5045 28.9741 19.047 29.741 19.66 30.45L19.93 30.75C20.9463 32.0109 22.1788 33.0809 23.57 33.91C24.7501 34.438 25.975 34.8597 27.23 35.17C27.82 35.33 28.39 35.49 28.95 35.67L30.6 36.32L30.81 36.43C30.7067 36.6753 30.5794 36.9097 30.43 37.13L30.3 37.34C29.7823 38.2036 29.5292 39.2 29.5718 40.206C29.6145 41.2119 29.9511 42.1833 30.54 43C31.0137 43.5936 31.5577 44.1275 32.16 44.59C32.8453 45.0399 33.3896 45.6742 33.73 46.42C33.8347 46.9583 33.8347 47.5117 33.73 48.05C33.38 51.21 32.88 54.38 32.26 57.49C32.2232 57.6542 32.2031 57.8217 32.2 57.99C31.8 57.99 31.39 57.99 30.98 57.99C23.8244 57.9847 16.9635 55.1391 11.9046 50.0784C6.84572 45.0176 4.00266 38.1556 4.00001 31ZM32.71 35.7L33.08 35.57C35.5554 35.0101 38.1477 35.3099 40.43 36.42C41.35 36.88 42.21 37.74 41.97 38.35C41.9189 38.4815 41.8962 38.6223 41.9034 38.7632C41.9106 38.9041 41.9475 39.0419 42.0117 39.1675C42.0759 39.2931 42.166 39.4037 42.276 39.4921C42.386 39.5804 42.5135 39.6444 42.65 39.68L48 41.09L48.37 41.18C48.3796 41.3097 48.3481 41.4392 48.28 41.55C48.1618 41.7439 48.028 41.9278 47.88 42.1C47.6947 42.3218 47.5243 42.5556 47.37 42.8C47.0741 43.3271 46.852 43.8924 46.71 44.48C46.6347 44.7612 46.5446 45.0383 46.44 45.31C45.9452 46.2818 45.2659 47.1479 44.44 47.86C41.61 50.67 38.72 53.48 35.85 56.21C35.41 56.6786 34.8924 57.0676 34.32 57.36C34.91 54.36 35.38 51.29 35.72 48.25C35.8529 47.4455 35.8223 46.6224 35.63 45.83C35.2049 44.6859 34.4409 43.6986 33.44 43C32.9639 42.6375 32.531 42.2214 32.15 41.76C31.802 41.2671 31.6029 40.6847 31.5763 40.082C31.5497 39.4793 31.6968 38.8816 32 38.36L32.12 38.17C32.404 37.8327 32.6052 37.4337 32.7076 37.0049C32.8101 36.5761 32.8109 36.1292 32.71 35.7ZM37.78 57.14C40.48 54.56 43.2 51.92 45.86 49.28C46.8789 48.3816 47.7055 47.2863 48.29 46.06C48.4205 45.7328 48.534 45.399 48.63 45.06C48.7287 44.6458 48.88 44.2459 49.08 43.87C49.1893 43.6991 49.3096 43.5354 49.44 43.38C49.661 43.1179 49.8584 42.8368 50.03 42.54C50.2782 42.137 50.3961 41.6672 50.3674 41.1947C50.3388 40.7222 50.1651 40.2701 49.87 39.9C49.497 39.5339 49.0236 39.2868 48.51 39.19L44 38C43.8666 37.2614 43.5493 36.5682 43.0774 35.9845C42.6056 35.4008 41.9943 34.9453 41.3 34.66C38.6058 33.3329 35.5352 32.9795 32.61 33.66C32.0363 33.829 31.4803 34.0534 30.95 34.33L30.34 34C30.34 32.22 29.84 31.84 29.64 31.68C29.3886 31.4954 29.0975 31.3721 28.79 31.32C29.0072 30.9528 29.0819 30.5187 29 30.1C28.66 28.86 27.13 29.18 26.23 29.37L25.94 29.43C25.8188 29.4539 25.6936 29.4462 25.5762 29.4076C25.4589 29.3691 25.3534 29.3011 25.27 29.21C25.162 29.0858 25.0863 28.9368 25.0497 28.7763C25.013 28.6158 25.0166 28.4488 25.06 28.29C25.21 27.67 25.44 27.47 26.75 27.29C27.87 27.1 29.3 27.43 29.75 28.4C29.8457 28.611 30.0117 28.7822 30.2196 28.8844C30.4276 28.9865 30.6645 29.0134 30.89 28.9602C31.1155 28.9071 31.3156 28.7773 31.456 28.5931C31.5965 28.4088 31.6686 28.1815 31.66 27.95C31.6775 26.3359 32.2736 24.7817 33.34 23.57C34.0285 22.7401 34.8792 22.0595 35.84 21.57L36.84 21L37.74 20.81C38.5585 20.6698 39.3669 20.476 40.16 20.23C40.8215 20.024 41.3795 19.5733 41.72 18.97C41.8025 18.7772 41.8451 18.5697 41.8451 18.36C41.8451 18.1503 41.8025 17.9428 41.72 17.75C41.6239 17.5049 41.4653 17.2892 41.2601 17.1243C41.0548 16.9595 40.8101 16.8511 40.55 16.81C40.2133 16.7651 39.8709 16.7956 39.5474 16.8994C39.224 17.0031 38.9277 17.1776 38.68 17.41H38.61C38.7502 17.2834 38.8806 17.1463 39 17C39.3077 16.7401 39.5562 16.4173 39.7287 16.0534C39.9011 15.6894 39.9937 15.2927 40 14.89C40 14.42 39 11 37 11C36.5027 11.045 36.0214 11.1986 35.59 11.45C35.3002 11.6043 34.9952 11.7282 34.68 11.82C34.4506 11.4236 34.092 11.1179 33.6643 10.9541C33.2366 10.7903 32.7656 10.7782 32.33 10.92C31.9239 11.05 31.57 11.3065 31.3199 11.6519C31.0699 11.9973 30.9368 12.4136 30.94 12.84C30.9894 13.48 30.8686 14.1217 30.59 14.7L26.41 12.3C26.276 12.2266 26.1459 12.1465 26.02 12.06C26.1537 11.743 26.3849 11.4768 26.68 11.3C27.46 10.73 28.22 10.15 28.98 9.57C29.74 8.99 30.44 8.46 31.16 7.93C31.5 7.68 31.84 7.59 32.03 7.69C32.22 7.79 32.41 8.27 32.34 9.05C32.3174 9.30911 32.3967 9.56682 32.561 9.76845C32.7253 9.97008 32.9617 10.0998 33.22 10.13C35.3 10.38 36.63 10 37.22 9.02C37.2814 8.91205 37.3317 8.79813 37.37 8.68C38.5523 9.24095 39.4879 10.2158 40 11.42C40 11.5 40.06 11.63 40.1 11.81C40.41 13.12 41.1 16.19 43.46 16.81C45.12 17.21 46.12 15.65 46.65 14.81L46.9 14.42C47.8659 13.3939 49.1278 12.6947 50.51 12.42L50.6 12.49C52.9076 14.9367 54.7389 17.7921 56 20.91C55.9107 20.9763 55.8271 21.0498 55.75 21.13C54.875 22.0368 54.2691 23.1689 54 24.4C53.9096 24.7341 53.8861 25.0827 53.9307 25.4259C53.9753 25.7691 54.0872 26.1001 54.26 26.4C55.1001 27.3205 56.2288 27.9272 57.46 28.12C56.1444 29.5587 54.9503 31.104 53.89 32.74C53.49 33.58 53.47 37 54.1 37.99C54.6096 38.7359 55.2185 39.4088 55.91 39.99C56.0323 40.0888 56.1762 40.1573 56.33 40.19C54.8348 44.3022 52.3667 47.9915 49.1365 50.943C45.9064 53.8946 42.01 56.0207 37.78 57.14ZM57 38.28C56.5633 37.9015 56.1738 37.4717 55.84 37C55.6141 35.9148 55.5701 34.7996 55.71 33.7C56.3921 32.6431 57.1543 31.6402 57.99 30.7C57.99 30.81 57.99 30.93 57.99 31.04C57.9906 33.4871 57.6575 35.9229 57 38.28Z" - fill="#326FA3" - /> - <path - d="M16.8602 43.73L15.4502 45.15C15.2632 45.3383 15.1587 45.5931 15.1597 45.8585C15.1601 45.9899 15.1865 46.1199 15.2372 46.2411C15.2879 46.3623 15.362 46.4724 15.4552 46.565C15.5485 46.6575 15.659 46.7309 15.7806 46.7807C15.9022 46.8306 16.0324 46.856 16.1638 46.8555C16.4291 46.8546 16.6833 46.7483 16.8702 46.56L18.2802 45.14C18.4672 44.9517 18.5717 44.6968 18.5708 44.4314C18.5698 44.1661 18.4635 43.9119 18.2752 43.725C18.0869 43.538 17.8321 43.4335 17.5667 43.4344C17.3013 43.4353 17.0472 43.5417 16.8602 43.73ZM22.1002 46.67C21.9861 46.6038 21.86 46.561 21.7292 46.5438C21.5984 46.5266 21.4656 46.5355 21.3382 46.5698C21.2109 46.6042 21.0916 46.6635 20.9873 46.7442C20.8829 46.8248 20.7956 46.9254 20.7302 47.04L19.7302 48.77C19.6579 48.8838 19.6093 49.0111 19.5874 49.1441C19.5655 49.2772 19.5708 49.4134 19.6029 49.5444C19.6349 49.6754 19.6932 49.7985 19.7741 49.9064C19.855 50.0143 19.9569 50.1047 20.0737 50.1722C20.1905 50.2397 20.3197 50.2829 20.4536 50.2991C20.5874 50.3154 20.7232 50.3044 20.8528 50.2668C20.9823 50.2293 21.1029 50.1659 21.2073 50.0805C21.3117 49.9951 21.3977 49.8895 21.4602 49.77L22.4602 48.04C22.5268 47.9264 22.5702 47.8009 22.5881 47.6705C22.6061 47.5401 22.5981 47.4075 22.5646 47.2802C22.5312 47.153 22.4729 47.0335 22.3932 46.9288C22.3136 46.8241 22.214 46.7361 22.1002 46.67ZM13.4802 35.43C13.4122 35.1743 13.2455 34.956 13.0169 34.823C12.7882 34.6899 12.5161 34.6529 12.2602 34.72L10.3302 35.24C10.1995 35.2704 10.0763 35.3269 9.96785 35.406C9.85942 35.485 9.76802 35.5851 9.69909 35.7003C9.63016 35.8154 9.58512 35.9433 9.56665 36.0762C9.54817 36.2092 9.55665 36.3445 9.59156 36.474C9.62648 36.6036 9.68712 36.7249 9.76987 36.8305C9.85263 36.9362 9.95581 37.0241 10.0733 37.089C10.1907 37.154 10.32 37.1946 10.4535 37.2085C10.587 37.2224 10.7219 37.2093 10.8502 37.17L12.7802 36.65C13.034 36.5798 13.2499 36.4123 13.381 36.1838C13.512 35.9554 13.5477 35.6845 13.4802 35.43ZM26.5702 48.52C26.3143 48.4529 26.0422 48.4899 25.8136 48.623C25.5849 48.756 25.4183 48.9743 25.3502 49.23L24.8302 51.16C24.7909 51.2883 24.7778 51.4232 24.7917 51.5567C24.8056 51.6902 24.8462 51.8195 24.9112 51.9369C24.9761 52.0544 25.064 52.1576 25.1697 52.2403C25.2753 52.3231 25.3966 52.3837 25.5262 52.4186C25.6557 52.4535 25.791 52.462 25.924 52.4435C26.0569 52.4251 26.1847 52.38 26.2999 52.3111C26.4151 52.2422 26.5152 52.1508 26.5942 52.0423C26.6733 51.9339 26.7298 51.8107 26.7602 51.68L27.2802 49.75C27.315 49.6226 27.3241 49.4896 27.3072 49.3586C27.2902 49.2277 27.2475 49.1014 27.1815 48.987C27.1155 48.8727 27.0275 48.7725 26.9226 48.6924C26.8177 48.6122 26.6979 48.5536 26.5702 48.52ZM15.3302 39.9C15.1986 39.6792 14.987 39.5175 14.7393 39.4486C14.4917 39.3797 14.227 39.4089 14.0002 39.53L12.2702 40.53C12.0526 40.6683 11.8968 40.8855 11.8354 41.136C11.7741 41.3865 11.8119 41.651 11.9409 41.8743C12.07 42.0976 12.2804 42.2624 12.5281 42.3343C12.7758 42.4061 13.0417 42.3795 13.2702 42.26L15.0002 41.26C15.2222 41.1215 15.3811 40.9016 15.4428 40.6473C15.5045 40.3931 15.4641 40.1248 15.3302 39.9Z" - fill="#326FA3" - /> - </svg> - ) -} diff --git a/src/components/total/_components/Progress.tsx b/src/components/total/_components/Progress.tsx deleted file mode 100644 index a02590a11..000000000 --- a/src/components/total/_components/Progress.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { useCurrentSimulation } from '@/publicodes-state' - -export default function Progress() { - const { progression } = useCurrentSimulation() - - return ( - <div - className="absolute bottom-0 left-0 right-0 top-0 origin-left bg-primary-700 transition-transform" - style={{ transform: `scaleX(${progression})` }} - /> - ) -} diff --git a/src/components/total/total/ButtonBack.tsx b/src/components/total/total/ButtonBack.tsx new file mode 100644 index 000000000..0163cbf40 --- /dev/null +++ b/src/components/total/total/ButtonBack.tsx @@ -0,0 +1,15 @@ +import ChevronLeft from '@/components/icons/ChevronLeft' + +export default function ButtonBack({ + onClick = () => {}, +}: { + onClick?: () => void +}) { + return ( + <button + className="relative block h-8 w-8 lg:absolute lg:-left-10 lg:top-1/2 lg:h-10 lg:w-10 lg:-translate-x-2 lg:-translate-y-1/2" + onClick={onClick}> + <ChevronLeft className="h-auto w-full transition-transform hover:-translate-x-2" /> + </button> + ) +} diff --git a/src/components/total/_components/Explanation.tsx b/src/components/total/total/Explanation.tsx similarity index 66% rename from src/components/total/_components/Explanation.tsx rename to src/components/total/total/Explanation.tsx index 181c1530b..69bce95c6 100644 --- a/src/components/total/_components/Explanation.tsx +++ b/src/components/total/total/Explanation.tsx @@ -7,42 +7,64 @@ import Badge from '@/design-system/layout/Badge' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useCurrentSimulation } from '@/publicodes-state' import { motion } from 'framer-motion' +import { useEffect, useState } from 'react' -type Props = { toggleOpen: any } - -export default function Explanation({ toggleOpen }: Props) { +export default function Explanation({ + toggleOpen, + isFirstToggle, +}: { + toggleOpen: () => void + isFirstToggle: boolean +}) { const { progression } = useCurrentSimulation() const { t } = useClientTranslation() + const [shouldRender, setShouldRender] = useState(!isFirstToggle) + + useEffect(() => { + if (isFirstToggle) { + const timer = setTimeout(() => { + setShouldRender(true) + }, 2000) + + return () => clearTimeout(timer) + } + }, [isFirstToggle]) + + if (!shouldRender) { + return null + } + return ( <motion.div - initial={{ opacity: 0, scale: 0 }} - animate={{ opacity: 1, scale: 1 }} - transition={{ duration: 0.2 }} - className="relative mb-2 mt-6 rounded-xl border-4 border-primary-700 p-4 pt-2"> + initial={{ opacity: 0, translateY: '-10px' }} + animate={{ opacity: 1, translateY: 0 }} + transition={{ duration: 0.3 }} + className="absolute left-2 top-0 z-50 mx-4 mb-2 w-full max-w-[calc(100%-2rem)] rounded-xl border-2 border-primary-200 bg-gray-100 p-3 pt-2 text-sm md:left-8 md:top-4 lg:w-2/3"> <svg width="28" height="24" viewBox="0 0 28 24" fill="none" xmlns="http://www.w3.org/2000/svg" - className="absolute bottom-full left-8 md:left-1/2 md:-translate-x-1/2 "> + className="absolute bottom-full left-8"> <path d="M14 0L27.8564 24H0.143594L14 0Z" - className=" fill-primary-700" + className=" fill-gray-100 stroke-primary-200 stroke-2" /> </svg> <div className="flex justify-end"> <button onClick={toggleOpen} - className="text-3xl leading-none" + className="h-3 w-3 bg-gray-100 text-xl leading-none" title={t('Fermer')}> × </button> </div> + {progression === 0 ? ( - <p className="mb-2 md:mb-4"> + <p className="mb-2"> <Trans i18nKey={'components.ScoreExplanation.text.p1'}> 🧮 Voici votre score de départ, calculé à partir de réponses attribuées à l'avance à chaque question ! Il évoluera à chaque @@ -50,27 +72,27 @@ export default function Explanation({ toggleOpen }: Props) { </Trans> </p> ) : ( - <p className="mb-2 md:mb-4"> + <p className="mb-2"> <Trans i18nKey={'components.ScoreExplanation.text.p2'}> 🧮 Voici votre score provisoire, il évolue à chaque nouvelle réponse ! </Trans> </p> )} - <p className="mb-2 md:mb-4"> + <p className="mb-2"> <Trans i18nKey={'components.ScoreExplanation.text.p3'}> 🤔 Si vous répondez "je ne sais pas" à une question, le score ne changera pas : une valeur par défaut vous est attribuée. </Trans> </p> - <p className="mb-2 md:mb-4"> + <p className="mb-2"> <Trans i18nKey={'components.ScoreExplanation.text.p4'}> 💡 Nous améliorons le calcul et ses valeurs par défaut{' '} <Link href="/nouveautes">tous les mois</Link>! </Trans> </p> <p className="mb-2 md:mb-4"> - <Badge color="secondary" size="sm"> + <Badge color="secondary" size="xs"> BETA </Badge>{' '} <Trans> @@ -79,6 +101,7 @@ export default function Explanation({ toggleOpen }: Props) { </p> <div className="flex justify-end"> <Button + size="xs" data-cypress-id="understood-explanation-button" onClick={toggleOpen}> <Trans>J'ai compris</Trans> diff --git a/src/components/total/total/Progress.tsx b/src/components/total/total/Progress.tsx new file mode 100644 index 000000000..a47913d8f --- /dev/null +++ b/src/components/total/total/Progress.tsx @@ -0,0 +1,30 @@ +import { + getBackgroundColor, + getBackgroundLightColor, +} from '@/helpers/getCategoryColorClass' +import { useCurrentSimulation, useForm } from '@/publicodes-state' +import { twMerge } from 'tailwind-merge' + +export default function Progress() { + const { currentCategory } = useForm() + + const { progression } = useCurrentSimulation() + + return ( + <> + <div + className={twMerge( + 'absolute bottom-0 left-0 right-0 h-1 transition-transform', + getBackgroundLightColor(currentCategory) + )} + /> + <div + className={twMerge( + 'absolute bottom-0 left-0 right-0 h-1 origin-left transition-transform', + getBackgroundColor(currentCategory) + )} + style={{ transform: `scaleX(${progression})` }} + /> + </> + ) +} diff --git a/src/components/total/total/TotalButtons.tsx b/src/components/total/total/TotalButtons.tsx new file mode 100644 index 000000000..6096c5736 --- /dev/null +++ b/src/components/total/total/TotalButtons.tsx @@ -0,0 +1,55 @@ +'use client' + +import ListIcon from '@/components/icons/ListIcon' +import SaveCheckIcon from '@/components/icons/SaveCheckIcon' +import SaveIcon from '@/components/icons/SaveIcon' +import Trans from '@/components/translation/Trans' +import Button from '@/design-system/inputs/Button' +import { useCurrentSimulation } from '@/publicodes-state' + +type Props = { + toggleQuestionList: () => void + toggleSaveModal?: () => void +} + +export default function TotalButtons({ + toggleQuestionList, + toggleSaveModal, +}: Props) { + const { savedViaEmail } = useCurrentSimulation() + + return ( + <div className="flex"> + <Button + color="text" + size="sm" + className="h-10 w-10 !p-0 font-medium lg:w-auto lg:gap-2 lg:!px-4 lg:!py-2" + onClick={() => { + toggleQuestionList() + }}> + <ListIcon className="h-6 w-6 fill-primary-700" /> + <span className="hidden lg:inline"> + <Trans>Liste des questions</Trans> + </span> + </Button> + {toggleSaveModal ? ( + <Button + color="text" + size="sm" + className="h-10 w-10 !p-0 font-medium lg:w-auto lg:gap-2 lg:!px-4 lg:!py-2" + onClick={() => { + toggleSaveModal() + }}> + {savedViaEmail ? ( + <SaveCheckIcon className="h-6 w-6 fill-primary-700" /> + ) : ( + <SaveIcon className="h-6 w-6 fill-primary-700" /> + )} + <span className="hidden lg:inline"> + <Trans>Reprendre plus tard</Trans> + </span> + </Button> + ) : null} + </div> + ) +} diff --git a/src/components/total/total/TotalFootprintNumber.tsx b/src/components/total/total/TotalFootprintNumber.tsx new file mode 100644 index 000000000..152005c67 --- /dev/null +++ b/src/components/total/total/TotalFootprintNumber.tsx @@ -0,0 +1,68 @@ +import Trans from '@/components/translation/Trans' +import { defaultMetric } from '@/constants/metric' +import { formatFootprint } from '@/helpers/formatters/formatFootprint' +import { useClientTranslation } from '@/hooks/useClientTranslation' +import { useLocale } from '@/hooks/useLocale' +import { useActions, useRule } from '@/publicodes-state' +import { Metric } from '@/publicodes-state/types' + +type Props = { + metric?: Metric +} + +const duration = { + carbone: <Trans>de CO₂e par an</Trans>, + eau: <Trans>d'eau par jour</Trans>, +} +export default function TotalFootprintNumber({ + metric = defaultMetric, +}: Props) { + const locale = useLocale() + const { t } = useClientTranslation() + + const { numericValue: totalFootprintValue } = useRule('bilan', metric) + + const { totalChosenActionsValue } = useActions() + + const totalFootprintValueMinusActions = + totalFootprintValue - totalChosenActionsValue + + const { formattedValue, unit } = formatFootprint( + totalFootprintValueMinusActions, + { + t, + locale, + metric, + } + ) + + const { formattedValue: formatedTotalFootprintValue } = formatFootprint( + totalFootprintValue, + { + t, + locale, + metric, + } + ) + + const shouldDisplayTotalWithoutActions = + totalFootprintValue !== totalFootprintValueMinusActions + + return ( + <div className="flex items-center gap-2 lg:block" aria-live="polite"> + {shouldDisplayTotalWithoutActions && ( + <strong className="mr-4 block font-black leading-none text-slate-500 line-through lg:inline lg:text-4xl short:text-3xl"> + {formatedTotalFootprintValue} + </strong> + )} + <strong className="block text-3xl font-black leading-none lg:inline lg:text-4xl short:text-3xl"> + {formattedValue} + </strong> + <span className="block text-xs font-medium leading-none lg:inline lg:text-base"> + {' '} + <Trans>{unit}</Trans> <br className="lg:hidden" /> + {duration[metric]} + </span> + </div> + ) +} diff --git a/src/components/translation/LanguageSwitchButton.tsx b/src/components/translation/LanguageSwitchButton.tsx index 4d85ca058..b35895d42 100644 --- a/src/components/translation/LanguageSwitchButton.tsx +++ b/src/components/translation/LanguageSwitchButton.tsx @@ -8,14 +8,12 @@ import { useIframe } from '@/hooks/useIframe' import i18nConfig from '@/i18nConfig' import { trackEvent } from '@/utils/matomo/trackEvent' import { useCurrentLocale } from 'next-i18n-router/client' -import { usePathname, useRouter, useSearchParams } from 'next/navigation' +import { usePathname, useSearchParams } from 'next/navigation' import { useCallback, useEffect } from 'react' export default function LanguageSwitchButton() { const { t } = useClientTranslation() - const router = useRouter() - const currentPathname = usePathname() const searchParams = useSearchParams().toString() @@ -47,22 +45,18 @@ export default function LanguageSwitchButton() { updateCookie(newLocale) if (currentLocale === i18nConfig.defaultLocale) { - router.push( + window.location.href = '/' + - newLocale + - currentPathname + - (searchParams.length > 0 ? `?${searchParams}` : '') - ) + newLocale + + currentPathname + + (searchParams.length > 0 ? `?${searchParams}` : '') } else { - router.push( + window.location.href = currentPathname.replace(`/${currentLocale}`, `/${newLocale}`) + - (searchParams.length > 0 ? `?${searchParams}` : '') - ) + (searchParams.length > 0 ? `?${searchParams}` : '') } - - router.refresh() }, - [currentLocale, currentPathname, router, searchParams] + [currentLocale, currentPathname, searchParams] ) // If the lang is fixed by the iframe and is not the same as the current locale, we change it here @@ -74,7 +68,7 @@ export default function LanguageSwitchButton() { }, [iframeLang, currentLocale, handleChange]) return ( - <div className="flex gap-2"> + <div className="flex flex-wrap gap-2"> <Button lang="fr" color={currentLocale === 'fr' ? 'primary' : 'secondary'} diff --git a/src/components/user/SaveSimulationForm.tsx b/src/components/user/SaveSimulationForm.tsx new file mode 100644 index 000000000..e33af518f --- /dev/null +++ b/src/components/user/SaveSimulationForm.tsx @@ -0,0 +1,252 @@ +'use client' + +import CheckCircleIcon from '@/components/icons/CheckCircleIcon' +import Trans from '@/components/translation/Trans' +import { + LIST_MAIN_NEWSLETTER, + LIST_NOS_GESTES_TRANSPORT_NEWSLETTER, +} from '@/constants/brevo' +import Button from '@/design-system/inputs/Button' +import CheckboxInputGroup from '@/design-system/inputs/CheckboxInputGroup' +import TextInputGroup from '@/design-system/inputs/TextInputGroup' +import Loader from '@/design-system/layout/Loader' +import Emoji from '@/design-system/utils/Emoji' +import { useGetNewsletterSubscriptions } from '@/hooks/settings/useGetNewsletterSubscriptions' +import { useUpdateUserSettings } from '@/hooks/settings/useUpdateUserSettings' +import { useClientTranslation } from '@/hooks/useClientTranslation' +import { useUser } from '@/publicodes-state' +import { ReactNode, useEffect, useRef, useState } from 'react' +import { SubmitHandler, useForm as useReactHookForm } from 'react-hook-form' +import { twMerge } from 'tailwind-merge' + +type Inputs = { + name: string + email?: string + 'newsletter-saisonniere': boolean + 'newsletter-transports': boolean +} + +type Props = { + title?: string | ReactNode + inputsDisplayed?: Array< + 'name' | 'email' | 'newsletter-saisonniere' | 'newsletter-transports' + > + submitLabel?: string | ReactNode + onCompleted?: (props: Record<string, unknown>) => void + className?: string + shouldForceEmailEditable?: boolean + defaultValues?: { + 'newsletter-transports': boolean + } +} + +export default function UserInformationForm({ + title, + inputsDisplayed = [ + 'name', + 'email', + 'newsletter-saisonniere', + 'newsletter-transports', + ], + submitLabel, + onCompleted = () => {}, + className, + shouldForceEmailEditable = false, + defaultValues, +}: Props) { + const [isSubmitted, setIsSubmitted] = useState(false) + + const { t } = useClientTranslation() + + const { user, updateEmail, updateName } = useUser() + + const timeoutRef = useRef<NodeJS.Timeout | null>(null) + + const { + register, + handleSubmit, + formState: { errors }, + setValue, + } = useReactHookForm<Inputs>({ + defaultValues: { + name: user?.name, + }, + }) + + const { data: newsletterSubscriptions } = useGetNewsletterSubscriptions( + user?.email ?? '' + ) + + useEffect(() => { + if (!newsletterSubscriptions && !defaultValues) return + + setValue( + 'newsletter-saisonniere', + newsletterSubscriptions?.includes(LIST_MAIN_NEWSLETTER) + ) + setValue( + 'newsletter-transports', + newsletterSubscriptions?.includes(LIST_NOS_GESTES_TRANSPORT_NEWSLETTER) || + defaultValues?.['newsletter-transports'] + ) + }, [newsletterSubscriptions, setValue, defaultValues]) + + const { + mutateAsync: updateUserSettings, + isPending, + isError, + } = useUpdateUserSettings({ + email: user?.email ?? '', + userId: user?.userId, + }) + + const onSubmit: SubmitHandler<Inputs> = async (data) => { + const newsletterIds = { + [LIST_MAIN_NEWSLETTER]: data['newsletter-saisonniere'], + [LIST_NOS_GESTES_TRANSPORT_NEWSLETTER]: data['newsletter-transports'], + } + + await updateUserSettings({ + name: data.name, + email: data.email, + newsletterIds, + }) + + if (data.email && (!user?.email || shouldForceEmailEditable)) { + updateEmail(data.email) + } + + if (data.name) { + updateName(data.name) + } + + setIsSubmitted(true) + + timeoutRef.current = setTimeout(() => { + setIsSubmitted(false) + onCompleted(data) + }, 2500) + } + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + }, []) + + return ( + <div className={twMerge('flex flex-col items-start', className)}> + {title ? title : null} + <form + onSubmit={handleSubmit(onSubmit)} + className="flex w-full flex-col items-start gap-4"> + {inputsDisplayed.includes('name') && ( + <TextInputGroup + value={user?.name} + label={t('Votre nom')} + {...register('name', { + required: user?.name ? t('Ce champ est requis.') : false, + })} + error={errors.name?.message} + /> + )} + + {inputsDisplayed.includes('email') && ( + <> + { + // On affiche le champ email en lecture seule si l'utilisateur a un email de défini + // sinon on lui permet d'en définir un + user?.email && !shouldForceEmailEditable ? ( + <TextInputGroup + name="email" + helperText={<Trans>Ce champ n'est pas modifiable</Trans>} + label={t('Votre adresse email')} + value={user?.email} + readOnly + /> + ) : ( + <TextInputGroup + label={t('Votre adresse email')} + className="w-full" + value={user?.email ?? ''} + {...register('email')} + /> + ) + } + </> + )} + {inputsDisplayed.includes('newsletter-saisonniere') || + (inputsDisplayed.includes('newsletter-transports') && ( + <> + <h3 className="mb-0 mt-6"> + <Trans>Inscription à nos e-mails</Trans> + </h3> + + <p className="text-sm text-gray-600"> + <Trans>Vous pouvez vous désincrire à tout moment</Trans> + </p> + </> + ))} + {inputsDisplayed.includes('newsletter-saisonniere') && ( + <CheckboxInputGroup + size="lg" + label={ + <span> + <Emoji>☀️</Emoji>{' '} + <Trans> + <strong>Infolettre saisonnière de Nos Gestes Climat</strong> : + actualités climat, initiatives positives et nouveautés + </Trans> + </span> + } + {...register('newsletter-saisonniere')} + /> + )} + {inputsDisplayed.includes('newsletter-transports') && ( + <CheckboxInputGroup + size="lg" + label={ + <span> + <Emoji>🚗</Emoji>{' '} + <Trans> + <strong>Nos Gestes Transports</strong> : tout savoir ou + presque sur l'impact carbone des transports, en 4 e-mails + </Trans> + </span> + } + {...register('newsletter-transports')} + /> + )} + + <div> + <Button + type="submit" + className="mt-6 gap-2 self-start" + disabled={isPending || isSubmitted}> + {isPending && <Loader size="sm" color="light" />} + + {submitLabel ?? <Trans>Mettre à jour mes informations</Trans>} + </Button> + + {isSubmitted && ( + <p className="mt-4 flex items-center text-sm text-green-700"> + <CheckCircleIcon className="mr-2 fill-green-700" /> + <Trans>Modifications sauvegardées</Trans> + </p> + )} + + {isError && ( + <p className="mt-4 text-sm text-red-700"> + <Trans> + Une erreur s'est produite au moment de la sauvegarde de vos + paramètres + </Trans> + </p> + )} + </div> + </form> + </div> + ) +} diff --git a/src/components/user/UserInformationForm.tsx b/src/components/user/UserInformationForm.tsx index 8c296cddc..5ede078b1 100644 --- a/src/components/user/UserInformationForm.tsx +++ b/src/components/user/UserInformationForm.tsx @@ -1,6 +1,5 @@ 'use client' -import CheckCircleIcon from '@/components/icons/CheckCircleIcon' import Trans from '@/components/translation/Trans' import { LIST_MAIN_NEWSLETTER, @@ -12,11 +11,13 @@ import CheckboxInputGroup from '@/design-system/inputs/CheckboxInputGroup' import TextInputGroup from '@/design-system/inputs/TextInputGroup' import Loader from '@/design-system/layout/Loader' import Emoji from '@/design-system/utils/Emoji' +import { displayErrorToast } from '@/helpers/toasts/displayErrorToast' +import { displaySuccessToast } from '@/helpers/toasts/displaySuccessToast' import { useGetNewsletterSubscriptions } from '@/hooks/settings/useGetNewsletterSubscriptions' import { useUpdateUserSettings } from '@/hooks/settings/useUpdateUserSettings' import { useClientTranslation } from '@/hooks/useClientTranslation' import { useUser } from '@/publicodes-state' -import { ReactNode, useEffect, useRef, useState } from 'react' +import { ReactNode, useEffect, useRef } from 'react' import { SubmitHandler, useForm as useReactHookForm } from 'react-hook-form' import { twMerge } from 'tailwind-merge' @@ -61,8 +62,6 @@ export default function UserInformationForm({ shouldForceEmailEditable = false, defaultValues, }: Props) { - const [isSubmitted, setIsSubmitted] = useState(false) - const { t } = useClientTranslation() const { user, updateEmail, updateName } = useUser() @@ -102,11 +101,7 @@ export default function UserInformationForm({ ) }, [newsletterSubscriptions, setValue, defaultValues]) - const { - mutateAsync: updateUserSettings, - isPending, - isError, - } = useUpdateUserSettings({ + const { mutateAsync: updateUserSettings, isPending } = useUpdateUserSettings({ email: user?.email ?? '', userId: user?.userId, }) @@ -118,26 +113,29 @@ export default function UserInformationForm({ [LIST_NOS_GESTES_LOGEMENT_NEWSLETTER]: data['newsletter-logement'], } - await updateUserSettings({ - name: data.name, - email: data.email, - newsletterIds, - }) + try { + await updateUserSettings({ + name: data.name, + email: data.email, + newsletterIds, + }) - if (data.email && (!user?.email || shouldForceEmailEditable)) { - updateEmail(data.email) - } + if (data.email && (!user?.email || shouldForceEmailEditable)) { + updateEmail(data.email) + } - if (data.name) { - updateName(data.name) - } + if (data.name) { + updateName(data.name) + } - setIsSubmitted(true) + displaySuccessToast(t('Vos informations ont bien été mises à jour.')) - timeoutRef.current = setTimeout(() => { - setIsSubmitted(false) - onCompleted(data) - }, 2500) + timeoutRef.current = setTimeout(() => { + onCompleted(data) + }, 2500) + } catch (error) { + displayErrorToast(t('Une erreur est survenue. Veuillez réessayer.')) + } } useEffect(() => { @@ -248,27 +246,11 @@ export default function UserInformationForm({ <Button type="submit" className="mt-6 gap-2 self-start" - disabled={isPending || isSubmitted}> + disabled={isPending}> {isPending && <Loader size="sm" color="light" />} {submitLabel ?? <Trans>Mettre à jour mes informations</Trans>} </Button> - - {isSubmitted && ( - <p className="mt-4 flex items-center text-sm text-green-700"> - <CheckCircleIcon className="mr-2 fill-green-700" /> - <Trans>Modifications sauvegardées</Trans> - </p> - )} - - {isError && ( - <p className="mt-4 text-sm text-red-700"> - <Trans> - Une erreur s'est produite au moment de la sauvegarde de vos - paramètres - </Trans> - </p> - )} </div> </form> </div> diff --git a/src/constants/orderedCategories.ts b/src/constants/orderedCategories.ts index af5f57d80..11533dad8 100644 --- a/src/constants/orderedCategories.ts +++ b/src/constants/orderedCategories.ts @@ -1,4 +1,6 @@ -export const orderedCategories = [ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' + +export const orderedCategories: DottedName[] = [ 'transport', 'alimentation', 'logement', diff --git a/src/constants/tracking/pages/actions.ts b/src/constants/tracking/pages/actions.ts index 083678061..dc4b30f31 100644 --- a/src/constants/tracking/pages/actions.ts +++ b/src/constants/tracking/pages/actions.ts @@ -1,7 +1,7 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' // Figma comment #67 export const actionsClickStart = ['trackEvent', 'Actions', 'Click Démarrer'] diff --git a/src/constants/tracking/pages/end.ts b/src/constants/tracking/pages/end.ts index b9f823fe5..cdc59e222 100644 --- a/src/constants/tracking/pages/end.ts +++ b/src/constants/tracking/pages/end.ts @@ -1,8 +1,8 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName } from '@/publicodes-state/types' import { NorthStarType, NorthStarValue } from '@/types/northstar' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' // Figma comment #57 export const endClickPoll = ['trackEvent', 'Fin', 'Click Poll'] diff --git a/src/constants/tracking/pages/profil.ts b/src/constants/tracking/pages/profil.ts index e7eaa24ac..66021b489 100644 --- a/src/constants/tracking/pages/profil.ts +++ b/src/constants/tracking/pages/profil.ts @@ -1,7 +1,7 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' // Figma comment #72 export const profilClickCtaCommencer = [ diff --git a/src/constants/tracking/pages/quiz.ts b/src/constants/tracking/pages/quiz.ts index 0ddd0f6ac..9e45bb716 100644 --- a/src/constants/tracking/pages/quiz.ts +++ b/src/constants/tracking/pages/quiz.ts @@ -1,7 +1,7 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' // Figma comment #120 export const quizClickPass = ['trackEvent', 'Quiz', 'Click Pass'] diff --git a/src/constants/tracking/question.ts b/src/constants/tracking/question.ts index 81aaf7bac..3351203c6 100644 --- a/src/constants/tracking/question.ts +++ b/src/constants/tracking/question.ts @@ -1,7 +1,7 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName, NodeValue } from '@/publicodes-state/types' +import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat' type Props = { question: DottedName diff --git a/src/constants/tracking/simulation.ts b/src/constants/tracking/simulation.ts index a957adb89..e05aa909f 100644 --- a/src/constants/tracking/simulation.ts +++ b/src/constants/tracking/simulation.ts @@ -1,7 +1,7 @@ // Return tracking data in format // [ 'trackEvent', 'Category', 'Action', 'Name', 'Value' ] -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' // Triggered when we see the first question of the category export const simulationCategoryStarted = (category: DottedName) => [ diff --git a/src/constants/urls.ts b/src/constants/urls.ts index 1209f384d..1538f5b7e 100644 --- a/src/constants/urls.ts +++ b/src/constants/urls.ts @@ -6,8 +6,6 @@ export const SERVER_URL = export const SIMULATION_URL = SERVER_URL + '/simulation/' -export const EMAIL_SIMULATION_URL = SERVER_URL + '/email-simulation/' - export const GROUP_URL = SERVER_URL + '/group' export const SAVE_SIMULATION_URL = SERVER_URL + '/simulations/create' diff --git a/src/design-system/inputs/Button.tsx b/src/design-system/inputs/Button.tsx index 1a3b9cffb..b0b60b190 100644 --- a/src/design-system/inputs/Button.tsx +++ b/src/design-system/inputs/Button.tsx @@ -11,15 +11,16 @@ export type ButtonProps = { disabled?: boolean id?: string title?: string + form?: string } & PropsWithChildren export const colorClassNames = { primary: - '!text-white bg-primary-700 border-2 border-primary-700 shadow-sm hover:text-white hover:bg-primary-800', + 'text-white bg-primary-700 border-2 border-primary-700 shadow-sm hover:text-white hover:bg-primary-800', secondary: 'border-solid border-primary-700 border-2 text-primary-700 bg-transparent shadow-sm hover:text-primary-700 hover:bg-primary-100 hover:border-primary-700', emerald: - '!text-logement-400 bg-emerald-light border-2 border-emerald-dark shadow-sm hover:text-white hover:bg-logement-400 hover:text-emerald-dark hover:border-emerald-dark', + 'text-logement-400 bg-emerald-light border-2 border-emerald-dark shadow-sm hover:text-white hover:bg-logement-400 hover:text-emerald-dark hover:border-emerald-dark', text: 'text-primary-700 bg-transparent border-2 border-transparent shadow-none hover:bg-primary-200 hover:text-primary-700 hover:border-primary-200', link: 'text-primary-700 bg-transparent border-2 border-transparent shadow-none hover:text-primary-700 underline !px-1', } @@ -45,6 +46,7 @@ export default function Button({ disabled, id, title, + form, ...props }: PropsWithChildren<ButtonProps & HtmlHTMLAttributes<HTMLButtonElement>>) { return ( @@ -59,6 +61,7 @@ export default function Button({ type={type} aria-disabled={disabled} title={title} + form={form} id={id} className={twMerge( baseClassNames, diff --git a/src/design-system/layout/Breadcrumbs.tsx b/src/design-system/layout/Breadcrumbs.tsx index 674f60e8d..b02242f53 100644 --- a/src/design-system/layout/Breadcrumbs.tsx +++ b/src/design-system/layout/Breadcrumbs.tsx @@ -4,20 +4,23 @@ import Link from '@/components/Link' import { breadcrumbClickLink } from '@/constants/tracking/layout' import { trackEvent } from '@/utils/matomo/trackEvent' import { Fragment } from 'react' +import { twMerge } from 'tailwind-merge' -type Props = { +export default function Breadcrumbs({ + items, + className, +}: { items: { href: string label: string | JSX.Element isActive?: boolean isDisabled?: boolean }[] -} - -export default function Breadcrumbs({ items }: Props) { + className?: string +}) { return ( - <section className="h-[75px] w-full bg-gray-100"> - <nav className="mx-auto flex h-full max-w-5xl items-center gap-4 overflow-x-scroll px-6 lg:px-0"> + <section className={twMerge('h-[75px] w-full', className)}> + <nav className="mx-auto flex h-full w-full items-center gap-4 overflow-x-scroll"> {items.map(({ href, label, isActive, isDisabled }, index) => ( <Fragment key={`breadcrumb-item-${index}`}> <Link diff --git a/src/design-system/layout/Main.tsx b/src/design-system/layout/Main.tsx index c6785554f..69babccd5 100644 --- a/src/design-system/layout/Main.tsx +++ b/src/design-system/layout/Main.tsx @@ -2,6 +2,7 @@ import { useIsClient } from '@/hooks/useIsClient' import { getIsIframe } from '@/utils/getIsIframe' +import { usePathname } from 'next/navigation' import { PropsWithChildren } from 'react' export default function Main({ @@ -15,10 +16,14 @@ export default function Main({ const isIframe = isClient && getIsIframe() + const pathname = usePathname() + return ( <main className={`flex flex-col overflow-hidden ${maxWidthClass} ${className} ${ - isIframe ? '' : 'min-h-[calc(100vh-2rem)]' + isIframe || pathname.includes('/simulateur/bilan') + ? '' + : 'min-h-[calc(100vh-2rem)]' }`}> {children} </main> diff --git a/src/design-system/layout/accordion/AccordionItem.tsx b/src/design-system/layout/accordion/AccordionItem.tsx index f350a5ddc..10b66e40a 100644 --- a/src/design-system/layout/accordion/AccordionItem.tsx +++ b/src/design-system/layout/accordion/AccordionItem.tsx @@ -1,4 +1,4 @@ -import ChevronRight from '@/design-system/icons/ChevronRight' +import ChevronRight from '@/components/icons/ChevronRight' import { motion } from 'framer-motion' import { ReactNode, useState } from 'react' @@ -29,13 +29,11 @@ export default function AccordionItem({ onClick() } }} - className={`relative z-10 flex w-full items-center justify-between border-gray-300 bg-white px-2 py-4 ${ - isOpen ? '' : 'border-b' - } ${isReadOnly ? '!cursor-default' : ''}`} + className={`relative z-10 flex w-full items-end justify-between py-2 ${isReadOnly ? '!cursor-default' : ''}`} aria-disabled={isReadOnly}> <div className="flex flex-1 items-center gap-4">{title}</div> - <div className="flex items-center gap-4 "> + <div className="flex items-center gap-4"> <ChevronRight className={`${isOpen ? 'rotate-90' : ''} ${ isReadOnly ? 'opacity-20' : '' diff --git a/src/design-system/modals/ConfirmationModal.tsx b/src/design-system/modals/ConfirmationModal.tsx index c3cc34f39..b50b5143c 100644 --- a/src/design-system/modals/ConfirmationModal.tsx +++ b/src/design-system/modals/ConfirmationModal.tsx @@ -43,7 +43,7 @@ export default function ConfirmationModal({ isOpen onRequestClose={closeModal} style={customStyles} - className="fixed left-1/2 top-1/2 w-[40rem] max-w-full -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-8" + className="fixed left-1/2 top-1/2 w-[40rem] max-w-[90vw] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-8" overlayClassName="fixed top-0 left-0 right-0 bottom-0 bg-black bg-opacity-50 z-[10000] overflow-hidden"> {children} diff --git a/src/design-system/modals/Modal.tsx b/src/design-system/modals/Modal.tsx index 223f39e85..b7cfa4044 100644 --- a/src/design-system/modals/Modal.tsx +++ b/src/design-system/modals/Modal.tsx @@ -1,5 +1,5 @@ import Trans from '@/components/translation/Trans' -import { ReactNode, useEffect, useRef, useState } from 'react' +import { ReactNode, useEffect } from 'react' import ReactModal from 'react-modal' import Button from '../inputs/Button' @@ -8,6 +8,8 @@ type Props = { children: ReactNode isLoading?: boolean isOpen: boolean + hasAbortButton?: boolean + buttons?: ReactNode } ReactModal.setAppElement('#modal') @@ -17,21 +19,9 @@ export default function Modal({ children, isLoading, isOpen, + hasAbortButton = true, + buttons, }: Props) { - const [localIsOpen, setLocalIsOpen] = useState(false) - - const customStyles = { - content: { - top: '50%', - left: '50%', - right: 'auto', - bottom: 'auto', - marginRight: '-50%', - transform: 'translate(-50%, -50%)', - borderRadius: '1rem', - }, - } - useEffect(() => { document.body.style.overflow = 'hidden' return () => { @@ -39,36 +29,26 @@ export default function Modal({ } }, []) - const timeoutRef = useRef<NodeJS.Timeout>() - - // Opening hook - useEffect(() => { - if (isOpen && !localIsOpen) { - timeoutRef.current = setTimeout(() => { - setLocalIsOpen(true) - }, 500) - } - - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - } - }, [isOpen, localIsOpen]) - return ( <ReactModal - isOpen={localIsOpen} - style={customStyles} - className="fixed left-1/2 top-1/2 w-[40rem] max-w-full -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-8" + isOpen={isOpen} + className="fixed left-1/2 top-1/2 w-[40rem] max-w-[90vw] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-white p-8 pt-16 md:pt-8" overlayClassName="fixed top-0 left-0 right-0 bottom-0 bg-black bg-opacity-50 z-[10000] overflow-hidden"> {children} - <div className="mt-12 flex justify-center"> - <Button color="secondary" onClick={!isLoading ? closeModal : () => {}}> - <Trans>Annuler</Trans> - </Button> - </div> + {hasAbortButton || buttons ? ( + <div className="mt-12 flex justify-between"> + {hasAbortButton && ( + <Button + color="secondary" + disabled={isLoading} + onClick={!isLoading ? closeModal : () => {}}> + <Trans>Annuler</Trans> + </Button> + )} + {buttons ? buttons : null} + </div> + ) : null} </ReactModal> ) } diff --git a/src/design-system/utils/BarChart.tsx b/src/design-system/utils/BarChart.tsx index 94b0a0fa7..12bdebd26 100644 --- a/src/design-system/utils/BarChart.tsx +++ b/src/design-system/utils/BarChart.tsx @@ -7,21 +7,20 @@ type Props = { color?: string } -export default function BarChart({ type, value, color, index = 0 }: Props) { +export default function BarChart({ type, value, color }: Props) { const propertyAffected = type === 'vertical' ? 'height' : 'width' return ( <motion.div - className={`min-w-[2px] max-w-full ${ - propertyAffected === 'width' ? 'h-[12px]' : 'w-[12px]' + className={`min-w-0.5 max-w-full ${ + propertyAffected === 'width' ? 'h-4' : 'w-4' } ${color ?? 'bg-secondary-700'} rotate-180 rounded-xl`} initial={{ [propertyAffected]: 0 }} animate={{ [propertyAffected]: value, }} transition={{ - delay: 0.3 + index * 0.1, - duration: 0.5, + duration: 0.2, ease: 'easeOut', }} /> diff --git a/src/design-system/utils/Emoji.tsx b/src/design-system/utils/Emoji.tsx index ccdc595ab..36762afbd 100644 --- a/src/design-system/utils/Emoji.tsx +++ b/src/design-system/utils/Emoji.tsx @@ -13,7 +13,7 @@ export default function Emoji({ <span aria-hidden alt="" - className={twMerge('inline-block', className)} + className={twMerge('*:inline-block', className)} {...props}> {emoji(children, { baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/svg', diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/filterIrrelevantActions.ts b/src/helpers/actions/filterIrrelevantActions.ts similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_helpers/filterIrrelevantActions.ts rename to src/helpers/actions/filterIrrelevantActions.ts diff --git a/src/helpers/actions/filterRelevantMissingVariables.ts b/src/helpers/actions/filterRelevantMissingVariables.ts new file mode 100644 index 000000000..c9b65b64c --- /dev/null +++ b/src/helpers/actions/filterRelevantMissingVariables.ts @@ -0,0 +1,38 @@ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' + +// Gathered from nosgestesclimat-site +const filteredDottedNames: DottedName[] = [ + 'divers . publicité', + 'services sociétaux . pression locale', + 'services sociétaux . voter', + 'divers . aider les autres', + 'divers . partage NGC', + 'transport . infolettre', + 'métrique', + 'déforestation', + 'logement . électricité verte', + 'logement . chauffage . électricité', + 'futureco-data . transport . ferry . distance aller . orthodromique', + 'futureco-data . transport . ferry . durée du voyage', + 'futureco-data . transport . ferry . vitesse en kmh', + 'futureco-data . transport . ferry . cabine', + 'futureco-data . transport . ferry . groupe', + 'futureco-data . transport . ferry . consommation de services', + 'futureco-data . transport . ferry . voiture', + 'services sociétaux . devenir producteur photovoltaique', + 'services sociétaux . bien placer argent', +] + +export const filterRelevantMissingVariables = ({ + missingVariables, + extendedFoldedSteps, +}: { + missingVariables: DottedName[] + extendedFoldedSteps: DottedName[] +}) => { + return missingVariables.filter((dottedName: DottedName) => { + const isFolded = extendedFoldedSteps.indexOf(dottedName) >= 0 + const isManuallyExcluded = !filteredDottedNames?.includes(dottedName) + return isManuallyExcluded && !isFolded + }) +} diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getActions.ts b/src/helpers/actions/getActions.ts similarity index 79% rename from src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getActions.ts rename to src/helpers/actions/getActions.ts index cd9a78b6a..0e7dafbf1 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getActions.ts +++ b/src/helpers/actions/getActions.ts @@ -1,18 +1,19 @@ import getSomme from '@/publicodes-state/helpers/getSomme' -import { DottedName, NGCEvaluatedNode } from '@/publicodes-state/types' -import { NGCRules } from '@incubateur-ademe/nosgestesclimat' - +import { NGCEvaluatedNode } from '@/publicodes-state/types' import { getCorrectedValue } from '@/utils/getCorrectedValue' import { sortBy } from '@/utils/sortBy' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { PublicodesExpression } from 'publicodes' import { filterIrrelevantActions } from './filterIrrelevantActions' import { getIsActionDisabled } from './getIsActionDisabled' type Props = { - rules: NGCRules + rules: any radical: boolean safeEvaluate: (rule: PublicodesExpression) => NGCEvaluatedNode | null - getRuleObject: (dottedName: DottedName) => any + getSpecialRuleObject: ( + dottedName: DottedName + ) => NGCEvaluatedNode & NGCRuleNode actionChoices: any } @@ -20,19 +21,19 @@ export default function getActions({ rules, radical, safeEvaluate, - getRuleObject, + getSpecialRuleObject, actionChoices, }: Props) { const actionsObject = rules.actions const somme = getSomme(actionsObject) ?? [] - const actions: any[] = somme + const actions = somme .filter( (actionRuleName: DottedName) => safeEvaluate({ 'est applicable': actionRuleName })?.nodeValue === true ) .map((actionRuleName: DottedName) => { - const ruleContent = getRuleObject(actionRuleName) + const ruleContent = getSpecialRuleObject(actionRuleName) return { ...ruleContent, dottedName: actionRuleName, diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getCarbonFootprint.ts b/src/helpers/actions/getCarbonFootprint.ts similarity index 91% rename from src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getCarbonFootprint.ts rename to src/helpers/actions/getCarbonFootprint.ts index fdf281b4d..7ba838224 100644 --- a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getCarbonFootprint.ts +++ b/src/helpers/actions/getCarbonFootprint.ts @@ -1,6 +1,6 @@ import { getCurrentLangInfos } from '@/locales/translation' -import { NodeValue } from '@/publicodes-state/types' import { TranslationFunctionType } from '@/types/translation' +import { NodeValue } from '@incubateur-ademe/nosgestesclimat' const getRawUnitDigitsArray = ({ value, @@ -24,8 +24,8 @@ const getRawUnitDigitsArray = ({ isConcise ? 't' : value > 2000 - ? (t('tonnes') as string) - : (t('tonne') as string), + ? (t('tonnes') as string) + : (t('tonne') as string), 1, ] } diff --git a/src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getIsActionDisabled.ts b/src/helpers/actions/getIsActionDisabled.ts similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/actions/_helpers/getIsActionDisabled.ts rename to src/helpers/actions/getIsActionDisabled.ts diff --git a/src/helpers/foldEveryQuestionsUntil.ts b/src/helpers/foldEveryQuestionsUntil.ts index 2d547fa9b..960cc3d86 100644 --- a/src/helpers/foldEveryQuestionsUntil.ts +++ b/src/helpers/foldEveryQuestionsUntil.ts @@ -1,8 +1,9 @@ import { UpdateCurrentSimulationProps } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { - question: string - relevantQuestions: string[] + question: DottedName + relevantQuestions: DottedName[] updateCurrentSimulation: (simulation: UpdateCurrentSimulationProps) => void } diff --git a/src/helpers/getCategoryColorClass.ts b/src/helpers/getCategoryColorClass.ts index 9a5370528..017529e1c 100644 --- a/src/helpers/getCategoryColorClass.ts +++ b/src/helpers/getCategoryColorClass.ts @@ -1,20 +1,3 @@ -export function getTextColor(category?: string | null) { - switch (category) { - case 'transport': - return `text-categories-text-transport` - case 'alimentation': - return `text-categories-text-alimentation` - case 'logement': - return `text-categories-text-logement` - case 'divers': - return `text-categories-text-divers` - case 'services sociétaux': - return `text-categories-text-servicessocietaux` - default: - return 'text-default' - } -} - export function getTextDarkColor(category?: string | null) { switch (category) { case 'transport': @@ -83,6 +66,23 @@ export function getBackgroundLightColor(category?: string | null) { } } +export function getBackgroundDarkColor(category?: string | null) { + switch (category) { + case 'transport': + return `bg-transport-700` + case 'alimentation': + return `bg-alimentation-700` + case 'logement': + return `bg-logement-700` + case 'divers': + return `bg-divers-700` + case 'services sociétaux': + return `bg-servicessocietaux-700` + default: + return 'bg-primary-700' + } +} + export function getBorderColor(category?: string | null) { switch (category) { case 'transport': @@ -99,6 +99,23 @@ export function getBorderColor(category?: string | null) { return 'border-primary-700' } } +export function getBorderLightColor(category?: string | null) { + switch (category) { + case 'transport': + return `border-transport-100` + case 'alimentation': + return `border-alimentation-100` + case 'logement': + return `border-logement-100` + case 'divers': + return `border-divers-100` + case 'services sociétaux': + return `border-servicessocietaux-100` + default: + return 'border-primary-100' + } +} + export function getFillColor(category?: string | null) { switch (category) { case 'transport': @@ -118,7 +135,6 @@ export function getFillColor(category?: string | null) { export function getCategoryColorClasses(category?: string | null) { return { - text: getTextColor(category), textDark: getTextDarkColor(category), background: getBackgroundColor(category), hoverBackground: getHoverBackgroundColor(category), @@ -127,3 +143,445 @@ export function getCategoryColorClasses(category?: string | null) { fill: getFillColor(category), } } + +export function getBgCategoryColor( + category?: string | null, + variation: + | '50' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '950' = '500' +) { + switch (category) { + case 'transport': + return `bg-transport-${variation}` + case 'alimentation': + return `bg-alimentation-${variation}` + case 'logement': + return `bg-logement-${variation}` + case 'divers': + return `bg-divers-${variation}` + case 'services sociétaux': + return `bg-servicessocietaux-${variation}` + default: + return `bg-primary-${variation}` + } +} + +export function getHoverBgCategoryColor( + category?: string | null, + variation: + | '50' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '950' = '300' +) { + switch (category) { + case 'transport': + switch (variation) { + case '50': + return 'hover:bg-transport-50' + case '100': + return 'hover:bg-transport-100' + case '200': + return 'hover:bg-transport-200' + case '300': + return 'hover:bg-transport-300' + case '400': + return 'hover:bg-transport-400' + case '500': + return 'hover:bg-transport-500' + case '600': + return 'hover:bg-transport-600' + case '700': + return 'hover:bg-transport-700' + case '800': + return 'hover:bg-transport-800' + case '900': + return 'hover:bg-transport-900' + case '950': + return 'hover:bg-transport-950' + } + break + case 'alimentation': + switch (variation) { + case '50': + return 'hover:bg-alimentation-50' + case '100': + return 'hover:bg-alimentation-100' + case '200': + return 'hover:bg-alimentation-200' + case '300': + return 'hover:bg-alimentation-300' + case '400': + return 'hover:bg-alimentation-400' + case '500': + return 'hover:bg-alimentation-500' + case '600': + return 'hover:bg-alimentation-600' + case '700': + return 'hover:bg-alimentation-700' + case '800': + return 'hover:bg-alimentation-800' + case '900': + return 'hover:bg-alimentation-900' + case '950': + return 'hover:bg-alimentation-950' + } + break + case 'logement': + switch (variation) { + case '50': + return 'hover:bg-logement-50' + case '100': + return 'hover:bg-logement-100' + case '200': + return 'hover:bg-logement-200' + case '300': + return 'hover:bg-logement-300' + case '400': + return 'hover:bg-logement-400' + case '500': + return 'hover:bg-logement-500' + case '600': + return 'hover:bg-logement-600' + case '700': + return 'hover:bg-logement-700' + case '800': + return 'hover:bg-logement-800' + case '900': + return 'hover:bg-logement-900' + case '950': + return 'hover:bg-logement-950' + } + break + case 'divers': + switch (variation) { + case '50': + return 'hover:bg-divers-50' + case '100': + return 'hover:bg-divers-100' + case '200': + return 'hover:bg-divers-200' + case '300': + return 'hover:bg-divers-300' + case '400': + return 'hover:bg-divers-400' + case '500': + return 'hover:bg-divers-500' + case '600': + return 'hover:bg-divers-600' + case '700': + return 'hover:bg-divers-700' + case '800': + return 'hover:bg-divers-800' + case '900': + return 'hover:bg-divers-900' + case '950': + return 'hover:bg-divers-950' + } + break + case 'services sociétaux': + switch (variation) { + case '50': + return 'hover:bg-servicessocietaux-50' + case '100': + return 'hover:bg-servicessocietaux-100' + case '200': + return 'hover:bg-servicessocietaux-200' + case '300': + return 'hover:bg-servicessocietaux-300' + case '400': + return 'hover:bg-servicessocietaux-400' + case '500': + return 'hover:bg-servicessocietaux-500' + case '600': + return 'hover:bg-servicessocietaux-600' + case '700': + return 'hover:bg-servicessocietaux-700' + case '800': + return 'hover:bg-servicessocietaux-800' + case '900': + return 'hover:bg-servicessocietaux-900' + case '950': + return 'hover:bg-servicessocietaux-950' + } + break + default: + switch (variation) { + case '50': + return 'hover:bg-primary-50' + case '100': + return 'hover:bg-primary-100' + case '200': + return 'hover:bg-primary-200' + case '300': + return 'hover:bg-primary-300' + case '400': + return 'hover:bg-primary-400' + case '500': + return 'hover:bg-primary-500' + case '600': + return 'hover:bg-primary-600' + case '700': + return 'hover:bg-primary-700' + case '800': + return 'hover:bg-primary-800' + case '900': + return 'hover:bg-primary-900' + case '950': + return 'hover:bg-primary-950' + } + } +} + +export function getBorderCategoryColor( + category?: string | null, + variation: + | '50' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '950' = '500' +) { + switch (category) { + case 'transport': + return `border-transport-${variation}` + case 'alimentation': + return `border-alimentation-${variation}` + case 'logement': + return `border-logement-${variation}` + case 'divers': + return `border-divers-${variation}` + case 'services sociétaux': + return `border-servicessocietaux-${variation}` + default: + return `border-primary-${variation}` + } +} + +export function getHoverBorderCategoryColor( + category?: string | null, + variation: + | '50' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '950' = '300' +) { + switch (category) { + case 'transport': + switch (variation) { + case '50': + return 'hover:border-transport-50' + case '100': + return 'hover:border-transport-100' + case '200': + return 'hover:border-transport-200' + case '300': + return 'hover:border-transport-300' + case '400': + return 'hover:border-transport-400' + case '500': + return 'hover:border-transport-500' + case '600': + return 'hover:border-transport-600' + case '700': + return 'hover:border-transport-700' + case '800': + return 'hover:border-transport-800' + case '900': + return 'hover:border-transport-900' + case '950': + return 'hover:border-transport-950' + } + break + case 'alimentation': + switch (variation) { + case '50': + return 'hover:border-alimentation-50' + case '100': + return 'hover:border-alimentation-100' + case '200': + return 'hover:border-alimentation-200' + case '300': + return 'hover:border-alimentation-300' + case '400': + return 'hover:border-alimentation-400' + case '500': + return 'hover:border-alimentation-500' + case '600': + return 'hover:border-alimentation-600' + case '700': + return 'hover:border-alimentation-700' + case '800': + return 'hover:border-alimentation-800' + case '900': + return 'hover:border-alimentation-900' + case '950': + return 'hover:border-alimentation-950' + } + break + case 'logement': + switch (variation) { + case '50': + return 'hover:border-logement-50' + case '100': + return 'hover:border-logement-100' + case '200': + return 'hover:border-logement-200' + case '300': + return 'hover:border-logement-300' + case '400': + return 'hover:border-logement-400' + case '500': + return 'hover:border-logement-500' + case '600': + return 'hover:border-logement-600' + case '700': + return 'hover:border-logement-700' + case '800': + return 'hover:border-logement-800' + case '900': + return 'hover:border-logement-900' + case '950': + return 'hover:border-logement-950' + } + break + case 'divers': + switch (variation) { + case '50': + return 'hover:border-divers-50' + case '100': + return 'hover:border-divers-100' + case '200': + return 'hover:border-divers-200' + case '300': + return 'hover:border-divers-300' + case '400': + return 'hover:border-divers-400' + case '500': + return 'hover:border-divers-500' + case '600': + return 'hover:border-divers-600' + case '700': + return 'hover:border-divers-700' + case '800': + return 'hover:border-divers-800' + case '900': + return 'hover:border-divers-900' + case '950': + return 'hover:border-divers-950' + } + break + case 'services sociétaux': + switch (variation) { + case '50': + return 'hover:border-servicessocietaux-50' + case '100': + return 'hover:border-servicessocietaux-100' + case '200': + return 'hover:border-servicessocietaux-200' + case '300': + return 'hover:border-servicessocietaux-300' + case '400': + return 'hover:border-servicessocietaux-400' + case '500': + return 'hover:border-servicessocietaux-500' + case '600': + return 'hover:border-servicessocietaux-600' + case '700': + return 'hover:border-servicessocietaux-700' + case '800': + return 'hover:border-servicessocietaux-800' + case '900': + return 'hover:border-servicessocietaux-900' + case '950': + return 'hover:border-servicessocietaux-950' + } + break + default: + switch (variation) { + case '50': + return 'hover:border-primary-50' + case '100': + return 'hover:border-primary-100' + case '200': + return 'hover:border-primary-200' + case '300': + return 'hover:border-primary-300' + case '400': + return 'hover:border-primary-400' + case '500': + return 'hover:border-primary-500' + case '600': + return 'hover:border-primary-600' + case '700': + return 'hover:border-primary-700' + case '800': + return 'hover:border-primary-800' + case '900': + return 'hover:border-primary-900' + case '950': + return 'hover:border-primary-950' + } + break + } +} + +export function getTextCategoryColor( + category?: string | null, + variation: + | '50' + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | '950' = '500' +) { + switch (category) { + case 'transport': + return `text-transport-${variation}` + case 'alimentation': + return `text-alimentation-${variation}` + case 'logement': + return `text-logement-${variation}` + case 'divers': + return `text-divers-${variation}` + case 'services sociétaux': + return `text-servicessocietaux-${variation}` + default: + return `text-primary-${variation}` + } +} diff --git a/src/helpers/groups/getTopThreeAndRestMembers.ts b/src/helpers/groups/getTopThreeAndRestMembers.ts index 71ef60e31..3c5ba1941 100644 --- a/src/helpers/groups/getTopThreeAndRestMembers.ts +++ b/src/helpers/groups/getTopThreeAndRestMembers.ts @@ -3,8 +3,8 @@ import { Participant } from '@/types/groups' export const getTopThreeAndRestMembers = (members: Participant[] = []) => { const sortedMembers = members.sort((memberA, memberB) => { - const totalA = memberA?.simulation.computedResults[defaultMetric].bilan - const totalB = memberB?.simulation.computedResults[defaultMetric].bilan + const totalA = memberA?.simulation?.computedResults?.[defaultMetric]?.bilan + const totalB = memberB?.simulation?.computedResults?.[defaultMetric]?.bilan return totalA !== undefined && totalB !== undefined ? totalA - totalB : -1 }) @@ -13,7 +13,8 @@ export const getTopThreeAndRestMembers = (members: Participant[] = []) => { (acc, member, index) => { if ( index < 3 && - member?.simulation?.computedResults[defaultMetric].bilan !== undefined + member?.simulation?.computedResults?.[defaultMetric]?.bilan !== + undefined ) { acc.topThreeMembers.push(member) } else { diff --git a/src/helpers/groups/getUserCategoryFootprintsSortedByDifference.ts b/src/helpers/groups/getUserCategoryFootprintsSortedByDifference.ts index adcd380ef..13edd57dd 100644 --- a/src/helpers/groups/getUserCategoryFootprintsSortedByDifference.ts +++ b/src/helpers/groups/getUserCategoryFootprintsSortedByDifference.ts @@ -1,6 +1,16 @@ -import { Points, ValueObject } from '@/types/groups' +import type { + CategoriesAndSubcategoriesFootprintsType, + PointsFortsFaiblesType, +} from '@/types/groups' -const sortByDifference = (a: Points, b: Points) => { +type Props = { + currentUserCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType +} + +const sortByDifference = ( + a: PointsFortsFaiblesType, + b: PointsFortsFaiblesType +) => { return Math.abs(b?.resultObject?.difference || 0) < Math.abs(a?.resultObject?.difference || 0) ? -1 @@ -8,12 +18,10 @@ const sortByDifference = (a: Points, b: Points) => { } export const getUserCategoryFootprintsSortedByDifference = ({ - userFootprintByCategoriesAndSubcategories, -}: { - userFootprintByCategoriesAndSubcategories: Record<string, ValueObject> -}) => { + currentUserCategoriesAndSubcategoriesFootprints, +}: Props) => { const filteredResult = Object.entries( - userFootprintByCategoriesAndSubcategories + currentUserCategoriesAndSubcategoriesFootprints ).filter( ([key, resultObject]) => !resultObject?.isCategory && @@ -24,20 +32,22 @@ export const getUserCategoryFootprintsSortedByDifference = ({ key !== 'services publics' ) - const formattedResult = filteredResult.map(([key, resultObject]) => ({ - key, - resultObject, - })) + const formattedResult: PointsFortsFaiblesType[] = filteredResult.map( + ([key, resultObject]) => ({ + key, + resultObject, + }) + ) const positiveDifferenceCategories = formattedResult.filter( ({ resultObject }) => - resultObject?.difference && resultObject?.difference < 0 - ) as Points[] + !!resultObject?.difference && resultObject?.difference < 0 + ) const negativeDifferenceCategories = formattedResult.filter( ({ resultObject }) => - resultObject?.difference && resultObject?.difference > 0 - ) as Points[] + !!resultObject?.difference && resultObject?.difference > 0 + ) return { positiveDifferenceCategoriesSorted: positiveDifferenceCategories.sort( diff --git a/src/helpers/localisation/sortSupportedRegions.ts b/src/helpers/localisation/sortSupportedRegions.ts index f5aa90045..7f6adae1e 100644 --- a/src/helpers/localisation/sortSupportedRegions.ts +++ b/src/helpers/localisation/sortSupportedRegions.ts @@ -1,5 +1,5 @@ import { - SupportedRegionType, + SupportedRegion, SupportedRegions, } from '@incubateur-ademe/nosgestesclimat' @@ -17,11 +17,11 @@ export const sortSupportedRegions = ({ return Object.fromEntries( Object.entries(supportedRegions).sort( (supportedRegionA, supportedRegionB) => { - const nameA = (supportedRegionA[1] as SupportedRegionType)[ + const nameA = (supportedRegionA[1] as SupportedRegion)[ currentLocale ]?.nom.toUpperCase() // ignore upper and lowercase - const nameB = (supportedRegionB[1] as SupportedRegionType)[ + const nameB = (supportedRegionB[1] as SupportedRegion)[ currentLocale ]?.nom.toUpperCase() // ignore upper and lowercase diff --git a/src/helpers/markdown/getPost.ts b/src/helpers/markdown/getPost.ts index 8e194c2ce..f628183c0 100644 --- a/src/helpers/markdown/getPost.ts +++ b/src/helpers/markdown/getPost.ts @@ -1,8 +1,12 @@ +import { Post } from '@/types/posts' import fs from 'fs' import matter from 'gray-matter' import path from 'path' -export async function getPost(folderPath: string, slug: string) { +export async function getPost( + folderPath: string, + slug: string +): Promise<Post | null> { const filePath = path.join(process.cwd(), folderPath + slug + '.mdx') try { const source = fs.readFileSync(filePath, 'utf-8') diff --git a/src/app/(layout-with-navigation)/stats/_helpers/matomo.js b/src/helpers/matomo.js similarity index 100% rename from src/app/(layout-with-navigation)/stats/_helpers/matomo.js rename to src/helpers/matomo.js diff --git a/src/helpers/modelFetching/getPersonas.ts b/src/helpers/modelFetching/getPersonas.ts index 14b8ecd4c..207895060 100644 --- a/src/helpers/modelFetching/getPersonas.ts +++ b/src/helpers/modelFetching/getPersonas.ts @@ -1,8 +1,7 @@ +import { Personas } from '@incubateur-ademe/nosgestesclimat' import personasEN from '@incubateur-ademe/nosgestesclimat/public/personas-en.json' import personasES from '@incubateur-ademe/nosgestesclimat/public/personas-es.json' import personasFR from '@incubateur-ademe/nosgestesclimat/public/personas-fr.json' - -import { Personas } from '@incubateur-ademe/nosgestesclimat' import { importPreviewFile } from './importPreviewFile' const personasByLocale: Record<string, Personas> = { diff --git a/src/helpers/navigation/simulateurPages.ts b/src/helpers/navigation/simulateurPages.ts index 1393ab867..138592f8b 100644 --- a/src/helpers/navigation/simulateurPages.ts +++ b/src/helpers/navigation/simulateurPages.ts @@ -1,4 +1,4 @@ -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { question?: DottedName diff --git a/src/app/(layout-with-navigation)/personas/_helpers/fixSituationWithPartialMosaic.ts b/src/helpers/personas/fixSituationWithPartialMosaic.ts similarity index 87% rename from src/app/(layout-with-navigation)/personas/_helpers/fixSituationWithPartialMosaic.ts rename to src/helpers/personas/fixSituationWithPartialMosaic.ts index c1a75de80..cd5b75f45 100644 --- a/src/app/(layout-with-navigation)/personas/_helpers/fixSituationWithPartialMosaic.ts +++ b/src/helpers/personas/fixSituationWithPartialMosaic.ts @@ -1,10 +1,6 @@ import getType from '@/publicodes-state/helpers/getType' -import { - DottedName, - NGCEvaluatedNode, - NGCRuleNode, - Situation, -} from '@/publicodes-state/types' +import { NGCEvaluatedNode, Situation } from '@/publicodes-state/types' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { PublicodesExpression } from 'publicodes' type Props = { @@ -18,7 +14,7 @@ const getMosaicChildrenGroup = ( situation: Situation, expectedMosaicGroup: DottedName[] ) => { - return Object.keys(situation).filter(([key]) => + return (Object.keys(situation) as DottedName[]).filter((key) => expectedMosaicGroup.includes(key) ) } diff --git a/src/app/(layout-with-navigation)/personas/_helpers/getPersonaFoldedSteps.ts b/src/helpers/personas/getPersonaFoldedSteps.ts similarity index 67% rename from src/app/(layout-with-navigation)/personas/_helpers/getPersonaFoldedSteps.ts rename to src/helpers/personas/getPersonaFoldedSteps.ts index 699c7bf59..691ad18bc 100644 --- a/src/app/(layout-with-navigation)/personas/_helpers/getPersonaFoldedSteps.ts +++ b/src/helpers/personas/getPersonaFoldedSteps.ts @@ -1,10 +1,11 @@ import { - DottedName, + Engine, + Entries, NGCEvaluatedNode, - NGCRuleNode, Situation, } from '@/publicodes-state/types' -import Engine, { PublicodesExpression } from 'publicodes' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' +import { PublicodesExpression } from 'publicodes' import { fixSituationWithPartialMosaic } from './fixSituationWithPartialMosaic' type Props = { @@ -42,31 +43,34 @@ export const getPersonaFoldedSteps = ({ // The persona folded steps are obtained by getting the missing variables and the situation variables. const personaFoldedSteps = [ ...Object.keys(engine.evaluate('bilan')?.missingVariables || {}).filter( - (missingVariable) => everyQuestions.includes(missingVariable) + (missingVariable) => + everyQuestions.includes(missingVariable as DottedName) ), ...Object.keys(safeSituation), - ] + ] as DottedName[] // Then, for each mosaic in the model, we remove all mosaic children and replace it with the rule mosaic itself // as we need the parent rule in the folded steps and not the children. // If we don't find any mosaic children for a given mosaic, we don't do anything. - Object.entries(everyMosaicChildrenWithParent).forEach( - ([mosaicParent, expectedMosaicGroup]) => { - let isMosaicInSituation = false + ;( + Object.entries(everyMosaicChildrenWithParent) as Entries< + typeof everyMosaicChildrenWithParent + > + ).forEach(([mosaicParent, expectedMosaicGroup]) => { + let isMosaicInSituation = false - expectedMosaicGroup.forEach((dottedName) => { - const index = personaFoldedSteps.indexOf(dottedName) - if (index > -1) { - personaFoldedSteps.splice(index, 1) - isMosaicInSituation = true - } - }) - - if (isMosaicInSituation) { - personaFoldedSteps.push(mosaicParent) + expectedMosaicGroup.forEach((dottedName) => { + const index = personaFoldedSteps.indexOf(dottedName) + if (index > -1) { + personaFoldedSteps.splice(index, 1) + isMosaicInSituation = true } + }) + + if (isMosaicInSituation) { + personaFoldedSteps.push(mosaicParent) } - ) + }) return personaFoldedSteps } diff --git a/src/helpers/publicodes/getRuleSumRules.ts b/src/helpers/publicodes/getRuleSumRules.ts index 27c312d4d..1c233b4cd 100644 --- a/src/helpers/publicodes/getRuleSumRules.ts +++ b/src/helpers/publicodes/getRuleSumRules.ts @@ -1,12 +1,12 @@ import getSomme from '@/publicodes-state/helpers/getSomme' -import { DottedName, NGCRuleNode } from '@/publicodes-state/types' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' export function getRuleSumRules(rule: NGCRuleNode): DottedName[] | undefined { const somme = getSomme(rule.rawNode) if (!somme) { - return undefined + return } - return somme.map((name: string) => `${rule.dottedName} . ${name}`) + return somme.map((name) => `${rule.dottedName} . ${name}` as DottedName) } diff --git a/src/helpers/publicodes/getRuleTitle.ts b/src/helpers/publicodes/getRuleTitle.ts index 001548ccd..a9e27ad42 100644 --- a/src/helpers/publicodes/getRuleTitle.ts +++ b/src/helpers/publicodes/getRuleTitle.ts @@ -1,5 +1,4 @@ -import { DottedName } from '@/publicodes-state/types' -import { NGCRule } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRule } from '@incubateur-ademe/nosgestesclimat' import { utils } from 'publicodes' export const getRuleTitle = ( diff --git a/src/helpers/simulation/compareTwoSimulations.ts b/src/helpers/simulation/compareTwoSimulations.ts index 5a7642650..6dc47a2c1 100644 --- a/src/helpers/simulation/compareTwoSimulations.ts +++ b/src/helpers/simulation/compareTwoSimulations.ts @@ -1,5 +1,6 @@ import { defaultMetric } from '@/constants/metric' import { Simulation } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' /** * Takes two Simulations and returns true if they are different, false otherwise. @@ -18,7 +19,10 @@ export function compareTwoSimulations( hasChanged = true } for (const key in simulation1.situation) { - if (simulation1.situation[key] !== simulation2.situation[key]) { + if ( + simulation1.situation[key as DottedName] !== + simulation2.situation[key as DottedName] + ) { hasChanged = true break } @@ -34,7 +38,10 @@ export function compareTwoSimulations( } } for (const key in simulation1.actionChoices) { - if (simulation1.actionChoices[key] !== simulation2.actionChoices[key]) { + if ( + simulation1.actionChoices[key as DottedName] !== + simulation2.actionChoices[key as DottedName] + ) { hasChanged = true break } @@ -50,8 +57,10 @@ export function compareTwoSimulations( } for (const key in simulation1.computedResults[defaultMetric].categories) { if ( - simulation1.computedResults[defaultMetric].categories[key] !== - simulation2.computedResults[defaultMetric].categories[key] + simulation1.computedResults[defaultMetric].categories[ + key as DottedName + ] !== + simulation2.computedResults[defaultMetric].categories[key as DottedName] ) { hasChanged = true break diff --git a/src/helpers/simulation/hasSimulationChanged.ts b/src/helpers/simulation/hasSimulationChanged.ts index c099a799e..cc85df4fe 100644 --- a/src/helpers/simulation/hasSimulationChanged.ts +++ b/src/helpers/simulation/hasSimulationChanged.ts @@ -1,5 +1,6 @@ import { defaultMetric } from '@/constants/metric' import { Simulation } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' export function areComputedResultsDifferent( simulation1?: Simulation, @@ -11,7 +12,11 @@ export function areComputedResultsDifferent( simulation1.computedResults[defaultMetric].bilan !== simulation2.computedResults[defaultMetric].bilan && (simulation1.computedResults[defaultMetric].categories - ? Object.keys(simulation1.computedResults[defaultMetric].categories).some( + ? ( + Object.keys( + simulation1.computedResults[defaultMetric].categories + ) as DottedName[] + ).some( (key) => simulation1.computedResults[defaultMetric].categories[key] !== simulation2.computedResults[defaultMetric].categories[key] diff --git a/src/helpers/toasts/displayErrorToast.ts b/src/helpers/toasts/displayErrorToast.ts index a595d2c46..0a428c85b 100644 --- a/src/helpers/toasts/displayErrorToast.ts +++ b/src/helpers/toasts/displayErrorToast.ts @@ -3,5 +3,7 @@ import { toast } from 'react-toastify' // Warning : this needs the ToastContainer to be rendered in the app // to work properly export function displayErrorToast(message: string) { - toast.error(message) + toast.error(message, { + autoClose: false, + }) } diff --git a/src/hooks/groups/__tests__/fixtures/createGroup.ts b/src/hooks/groups/__tests__/fixtures/createGroup.ts new file mode 100644 index 000000000..53e623337 --- /dev/null +++ b/src/hooks/groups/__tests__/fixtures/createGroup.ts @@ -0,0 +1,41 @@ +import { Participant } from '@/types/groups' +import { faker } from '@faker-js/faker' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import personas from '@incubateur-ademe/nosgestesclimat/public/personas-fr.json' + +export function createGroup({ + participants, + currentUserId, +}: { + participants: Partial<Participant>[] + currentUserId: string +}) { + return { + participants: participants.map((p, index) => ({ + // We set the first participant as the current user + userId: !index ? currentUserId : faker.string.uuid(), + simulation: { + id: faker.string.uuid(), + date: faker.date.recent().toISOString(), + foldedSteps: [], + actionChoices: {}, + computedResults: { + carbone: { + bilan: faker.number.float(), + categories: {} as Record<DottedName, number>, + }, + eau: { + bilan: faker.number.float(), + categories: {} as Record<DottedName, number>, + }, + }, + + progression: 1, + situation: + personas[`personas . ${p.name}` as keyof typeof personas].situation, + }, + name: p.name || faker.name.firstName(), + _id: faker.database.mongodbObjectId(), + })), + } +} diff --git a/src/hooks/groups/__tests__/useGetGroupStats.spec.ts b/src/hooks/groups/__tests__/useGetGroupStats.spec.ts new file mode 100644 index 000000000..a0062cc0e --- /dev/null +++ b/src/hooks/groups/__tests__/useGetGroupStats.spec.ts @@ -0,0 +1,84 @@ +jest.mock('@/publicodes-state') + +import { useGetGroupStats } from '@/hooks/groups/useGetGroupStats' +import { faker } from '@faker-js/faker' +import { createGroup } from './fixtures/createGroup' + +describe('useGetGroupStats', () => { + describe('given a group with 2 participants that have different carbon footprint', () => { + let group: ReturnType<typeof createGroup> + const currentUserId = faker.string.uuid() + + beforeEach(() => { + group = createGroup({ + participants: [ + { + name: 'nolan', + }, + { + name: 'corentin', + }, + ], + currentUserId, + }) + }) + + describe('when the points forts and points faibles are compared', () => { + let result: ReturnType<typeof useGetGroupStats> + + beforeEach(() => { + result = useGetGroupStats({ + groupMembers: group.participants, + userId: currentUserId, + }) + }) + + it('then it should return 2 points fort and 3 points faibles', () => { + expect(result.pointsForts).toHaveLength(2) + expect(result.pointsFaibles).toHaveLength(3) + }) + }) + }) + + describe('given a group with 3 participants that have different carbon footprints', () => { + let group: ReturnType<typeof createGroup> + const currentUserId = faker.string.uuid() + + beforeEach(() => { + group = createGroup({ + participants: [ + { + name: 'nolan', + }, + { + name: 'corentin', + }, + { + name: 'sandy', + }, + ], + currentUserId, + }) + }) + + describe('when the points forts and points faibles are compared', () => { + let result: ReturnType<typeof useGetGroupStats> + + beforeEach(() => { + result = useGetGroupStats({ + groupMembers: group.participants, + userId: currentUserId, + }) + }) + + it('then it should return pointsForts and pointFaibles that have a difference value !== 0', () => { + expect( + result.pointsForts.every((p) => p.resultObject.difference !== 0) + ).toBeTruthy() + expect( + result.pointsFaibles.every((p) => p.resultObject.difference !== 0) + ).toBeTruthy() + }) + }) + }) +}) diff --git a/src/hooks/groups/useGetGroupAndUserFootprints.ts b/src/hooks/groups/useGetGroupAndUserFootprints.ts index fb00b3610..3901e4496 100644 --- a/src/hooks/groups/useGetGroupAndUserFootprints.ts +++ b/src/hooks/groups/useGetGroupAndUserFootprints.ts @@ -1,8 +1,11 @@ import { orderedCategories } from '@/constants/orderedCategories' import { getRuleSumRules } from '@/helpers/publicodes/getRuleSumRules' import { useDisposableEngine, useTempEngine } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' -import { Participant } from '@/types/groups' +import { + CategoriesAndSubcategoriesFootprintsType, + Participant, +} from '@/types/groups' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' type Props = { groupMembers: Participant[] @@ -11,19 +14,24 @@ type Props = { export function getSubcategories({ category, - getRuleObject, + getSpecialRuleObject, }: { - category: string - getRuleObject: (dottedName: DottedName) => any + category: DottedName + getSpecialRuleObject: (dottedName: DottedName) => NGCRuleNode }): DottedName[] | undefined { - const rule = getRuleObject(category) + const rule = getSpecialRuleObject(category) + return getRuleSumRules(rule) } + export const useGetGroupAndUserFootprints = ({ groupMembers, userId, -}: Props) => { - const { rules, getRuleObject } = useTempEngine() +}: Props): { + currentUserCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + groupCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType +} => { + const { rules, getSpecialRuleObject } = useTempEngine() const { getValue, updateSituation } = useDisposableEngine({ rules, @@ -33,8 +41,8 @@ export const useGetGroupAndUserFootprints = ({ return groupMembers.reduce( ( { - groupFootprintByCategoriesAndSubcategories, - userFootprintByCategoriesAndSubcategories, + groupCategoriesAndSubcategoriesFootprints, + currentUserCategoriesAndSubcategoriesFootprints, }, groupMember: Participant ) => { @@ -43,61 +51,70 @@ export const useGetGroupAndUserFootprints = ({ updateSituation(groupMember?.simulation?.situation || {}) // Create a copy of the accumulator - const updatedGroupFootprintByCategoriesAndSubcategories = { - ...groupFootprintByCategoriesAndSubcategories, - } as any + const updatedGroupCategoriesAndSubcategoriesFootprints = { + ...groupCategoriesAndSubcategoriesFootprints, + } - const updatedUserFootprintByCategoriesAndSubcategories = { - ...userFootprintByCategoriesAndSubcategories, - } as any + const updatedCurrentUserCategoriesAndSubcategoriesFootprints = { + ...currentUserCategoriesAndSubcategoriesFootprints, + } - orderedCategories.forEach((category: any) => { - const categoryValue = getValue(category) + orderedCategories.forEach((category) => { + const categoryRawValue = getValue(category) + + const categoryValue = + typeof categoryRawValue === 'number' ? categoryRawValue : 0 const defaultCategoryObject = { name: category, - value: categoryValue, + value: categoryValue ?? 0, isCategory: true, } - // If the category is not in the accumulator, we add its name as a new key in the object along with its value - // otherwise we add the value to the existing sum - if (!updatedGroupFootprintByCategoriesAndSubcategories[category]) { - updatedGroupFootprintByCategoriesAndSubcategories[category] = + // If the category is not in the accumulator, we add its name + // as a new key in the object along with its value otherwise we + // add the value to the existing sum + if (!updatedGroupCategoriesAndSubcategoriesFootprints[category]) { + updatedGroupCategoriesAndSubcategoriesFootprints[category] = defaultCategoryObject } else { - updatedGroupFootprintByCategoriesAndSubcategories[category].value += - categoryValue + updatedGroupCategoriesAndSubcategoriesFootprints[category].value += + typeof categoryValue === 'number' ? categoryValue : 0 } // Add each category footprint for the current member if (isCurrentMember) { - updatedUserFootprintByCategoriesAndSubcategories[category] = + updatedCurrentUserCategoriesAndSubcategoriesFootprints[category] = defaultCategoryObject } const currentCategorySubcategories = - getSubcategories({ category, getRuleObject }) || [] + getSubcategories({ category, getSpecialRuleObject }) || [] + + currentCategorySubcategories.forEach((subCategory) => { + const subCategoryRawValue = getValue(subCategory) - currentCategorySubcategories.forEach((subCategory: string) => { - const subCategoryValue = getValue(subCategory) + const subCategoryValue = + typeof subCategoryRawValue === 'number' ? subCategoryRawValue : 0 // Same here if the property doesn't exist in the accumulator, we add it // otherwise we add the value to the existing sum - if (!updatedGroupFootprintByCategoriesAndSubcategories[subCategory]) { - updatedGroupFootprintByCategoriesAndSubcategories[subCategory] = { + if (!updatedGroupCategoriesAndSubcategoriesFootprints[subCategory]) { + updatedGroupCategoriesAndSubcategoriesFootprints[subCategory] = { name: subCategory, value: subCategoryValue, } } else { - updatedGroupFootprintByCategoriesAndSubcategories[ + updatedGroupCategoriesAndSubcategoriesFootprints[ subCategory ].value += subCategoryValue } if (isCurrentMember) { // Add each category footprint for the current member - updatedUserFootprintByCategoriesAndSubcategories[subCategory] = { + updatedCurrentUserCategoriesAndSubcategoriesFootprints[ + subCategory + ] = { name: subCategory, value: subCategoryValue, } @@ -106,15 +123,18 @@ export const useGetGroupAndUserFootprints = ({ }) return { - groupFootprintByCategoriesAndSubcategories: - updatedGroupFootprintByCategoriesAndSubcategories, - userFootprintByCategoriesAndSubcategories: - updatedUserFootprintByCategoriesAndSubcategories, + groupCategoriesAndSubcategoriesFootprints: + updatedGroupCategoriesAndSubcategoriesFootprints, + currentUserCategoriesAndSubcategoriesFootprints: + updatedCurrentUserCategoriesAndSubcategoriesFootprints, } }, { - groupFootprintByCategoriesAndSubcategories: {}, - userFootprintByCategoriesAndSubcategories: {}, + groupCategoriesAndSubcategoriesFootprints: {}, + currentUserCategoriesAndSubcategoriesFootprints: {}, + } as { + groupCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + currentUserCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType } ) } diff --git a/src/hooks/groups/useGetGroupStats.ts b/src/hooks/groups/useGetGroupStats.ts index f91d1b1c1..ed5484a2e 100644 --- a/src/hooks/groups/useGetGroupStats.ts +++ b/src/hooks/groups/useGetGroupStats.ts @@ -1,71 +1,87 @@ import { getUserCategoryFootprintsSortedByDifference } from '@/helpers/groups/getUserCategoryFootprintsSortedByDifference' -import { Participant, Points, Results, ValueObject } from '@/types/groups' +import { + CategoriesAndSubcategoriesFootprintsType, + Participant, + PointsFortsFaiblesType, +} from '@/types/groups' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useGetGroupAndUserFootprints } from './useGetGroupAndUserFootprints' type Props = { groupMembers: Participant[] userId: string } + +type ResultsType = { + currentUserCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + groupCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + pointsForts: PointsFortsFaiblesType[] + pointsFaibles: PointsFortsFaiblesType[] +} + export const useGetGroupStats = ({ groupMembers, userId }: Props) => { const { - groupFootprintByCategoriesAndSubcategories, - userFootprintByCategoriesAndSubcategories, + groupCategoriesAndSubcategoriesFootprints, + currentUserCategoriesAndSubcategoriesFootprints, } = useGetGroupAndUserFootprints({ groupMembers, userId, }) - const results = { - userFootprintByCategoriesAndSubcategories: {} as Record< - string, - ValueObject - >, - groupFootprintByCategoriesAndSubcategories: {} as Record< - string, - ValueObject - >, - pointsForts: {} as Points[], - pointsFaibles: {} as Points[], - } - - results.groupFootprintByCategoriesAndSubcategories = { - ...groupFootprintByCategoriesAndSubcategories, - } - results.userFootprintByCategoriesAndSubcategories = { - ...userFootprintByCategoriesAndSubcategories, + const results: ResultsType = { + currentUserCategoriesAndSubcategoriesFootprints: { + ...currentUserCategoriesAndSubcategoriesFootprints, + }, + groupCategoriesAndSubcategoriesFootprints: { + ...groupCategoriesAndSubcategoriesFootprints, + }, + pointsForts: [], + pointsFaibles: [], } // Calculate the mean for the group for each category - Object.keys(groupFootprintByCategoriesAndSubcategories).forEach((key) => { - // Calculate mean for the group for each category - results.groupFootprintByCategoriesAndSubcategories[key].mean = - results.groupFootprintByCategoriesAndSubcategories[key].value / - groupMembers.length - }) + Object.keys(results.groupCategoriesAndSubcategoriesFootprints).forEach( + (key) => { + const typedKey = key as DottedName + + // Calculate mean for the group for each category + results.groupCategoriesAndSubcategoriesFootprints[typedKey].mean = + results.groupCategoriesAndSubcategoriesFootprints[typedKey].value / + groupMembers.length + } + ) // Calculate the current user variation between its value and the group mean for each category // and subcategory - Object.keys(userFootprintByCategoriesAndSubcategories).forEach((key) => { - results.userFootprintByCategoriesAndSubcategories[key].difference = - getDifference({ - value: results.userFootprintByCategoriesAndSubcategories[key].value, + Object.keys(results.currentUserCategoriesAndSubcategoriesFootprints).forEach( + (key) => { + const typedKey = key as DottedName + + results.currentUserCategoriesAndSubcategoriesFootprints[ + typedKey + ].difference = getDifference({ + value: + results.currentUserCategoriesAndSubcategoriesFootprints[typedKey] + .value, mean: - results.groupFootprintByCategoriesAndSubcategories[key]?.mean || 0, + results.groupCategoriesAndSubcategoriesFootprints[typedKey]?.mean ?? + 0, }) - }) + } + ) const { positiveDifferenceCategoriesSorted, negativeDifferenceCategoriesSorted, } = getUserCategoryFootprintsSortedByDifference({ - userFootprintByCategoriesAndSubcategories: - results.userFootprintByCategoriesAndSubcategories, + currentUserCategoriesAndSubcategoriesFootprints: + results.currentUserCategoriesAndSubcategoriesFootprints, }) results.pointsForts = positiveDifferenceCategoriesSorted.slice(0, 2) results.pointsFaibles = negativeDifferenceCategoriesSorted.slice(0, 3) - return results as Results + return results } const getDifference = ({ value, mean }: { value: number; mean: number }) => { diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useFetchOrganisation.ts b/src/hooks/organisations/useFetchOrganisation.ts similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useFetchOrganisation.ts rename to src/hooks/organisations/useFetchOrganisation.ts diff --git a/src/hooks/organisations/useIsOrganisationAdmin.ts b/src/hooks/organisations/useIsOrganisationAdmin.ts index 354f290e8..9e1a724eb 100644 --- a/src/hooks/organisations/useIsOrganisationAdmin.ts +++ b/src/hooks/organisations/useIsOrganisationAdmin.ts @@ -1,6 +1,6 @@ 'use client' -import useFetchOrganisation from '@/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useFetchOrganisation' +import useFetchOrganisation from '@/hooks/organisations/useFetchOrganisation' import { useUser } from '@/publicodes-state' import { useParams } from 'next/navigation' diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useTimeleft.ts b/src/hooks/organisations/useTimeleft.ts similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useTimeleft.ts rename to src/hooks/organisations/useTimeleft.ts diff --git a/src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useValidateVerificationCode.ts b/src/hooks/organisations/useValidateVerificationCode.ts similarity index 100% rename from src/app/(layout-with-navigation)/(simulation)/organisations/_hooks/useValidateVerificationCode.ts rename to src/hooks/organisations/useValidateVerificationCode.ts diff --git a/src/hooks/quiz/useSaveQuizAnswer.ts b/src/hooks/quiz/useSaveQuizAnswer.ts index 932a335cd..cd61de600 100644 --- a/src/hooks/quiz/useSaveQuizAnswer.ts +++ b/src/hooks/quiz/useSaveQuizAnswer.ts @@ -1,7 +1,7 @@ import { SERVER_URL } from '@/constants/urls' import { useCurrentSimulation } from '@/publicodes-state' -import { DottedName } from '@/publicodes-state/types' import { AnswerType } from '@/types/quiz' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMutation } from '@tanstack/react-query' import axios from 'axios' diff --git a/src/hooks/simulation/useSaveSimulation.ts b/src/hooks/simulation/useSaveSimulation.ts index 3e231430d..5310a1634 100644 --- a/src/hooks/simulation/useSaveSimulation.ts +++ b/src/hooks/simulation/useSaveSimulation.ts @@ -16,8 +16,7 @@ export function useSaveSimulation() { const { resetSyncTimer } = useBackgroundSyncSimulation() const { - mutateAsync: saveSimulation, - mutate: saveSimulationNotAsync, + mutate: saveSimulation, isPending, isSuccess, isError, @@ -44,12 +43,10 @@ export function useSaveSimulation() { listIds, }) .then((response) => response.data) - .catch(() => console.error('Failed to save simulation')) }, }) return { saveSimulation, - saveSimulationNotAsync, isPending, isSuccess, isError, diff --git a/src/hooks/simulation/useSetCurrentSimulationFromParams.ts b/src/hooks/simulation/useSetCurrentSimulationFromParams.ts index 3d42f2d21..16425efaf 100644 --- a/src/hooks/simulation/useSetCurrentSimulationFromParams.ts +++ b/src/hooks/simulation/useSetCurrentSimulationFromParams.ts @@ -50,6 +50,9 @@ export function useSetCurrentSimulationFromParams() { // if the simulation is not in the localStorage, we add it initSimulation(simulation) setIsCorrectSimulationSet(true) + + // We delete the query params and reload the page + window.location.href = pathname }, [ simulations, simulation, diff --git a/src/hooks/tracking/useTrackSimulateur.ts b/src/hooks/tracking/useTrackSimulateur.ts index 4f1b175b9..be3e395a6 100644 --- a/src/hooks/tracking/useTrackSimulateur.ts +++ b/src/hooks/tracking/useTrackSimulateur.ts @@ -30,11 +30,14 @@ export function useTrackSimulateur() { }, [progression]) useEffect(() => { + if (!currentCategory) return + if (isFirstQuestionOfCategory) { - trackEvent(simulationCategoryStarted(currentCategory || '')) + trackEvent(simulationCategoryStarted(currentCategory)) } + if (isLastQuestionOfCategory) { - trackEvent(simulationCategoryCompleted(currentCategory || '')) + trackEvent(simulationCategoryCompleted(currentCategory)) } }, [currentCategory, isFirstQuestionOfCategory, isLastQuestionOfCategory]) } diff --git a/src/hooks/useQuestionInQueryParams.ts b/src/hooks/useQuestionInQueryParams.ts index dd70791e0..ad4e15579 100644 --- a/src/hooks/useQuestionInQueryParams.ts +++ b/src/hooks/useQuestionInQueryParams.ts @@ -1,4 +1,5 @@ import { getLinkToSimulateur } from '@/helpers/navigation/simulateurPages' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback } from 'react' @@ -9,10 +10,10 @@ export const useQuestionInQueryParams = () => { const questionInQueryParams = decodeURI(searchParams.get('question') || '') ?.replaceAll('.', ' . ') - .replaceAll('_', ' ') + .replaceAll('_', ' ') as DottedName const setQuestionInQueryParams = useCallback( - (question: string) => + (question: DottedName) => router.replace( getLinkToSimulateur({ question }), diff --git a/src/hooks/useSortedSubcategoriesByFootprint.ts b/src/hooks/useSortedSubcategoriesByFootprint.ts index 593d81b79..1881be95b 100644 --- a/src/hooks/useSortedSubcategoriesByFootprint.ts +++ b/src/hooks/useSortedSubcategoriesByFootprint.ts @@ -1,5 +1,6 @@ import { useEngine, useSimulation } from '@/publicodes-state' -import { DottedName, Metric } from '@/publicodes-state/types' +import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' type Props = { @@ -19,12 +20,12 @@ export function useSortedSubcategoriesByFootprint({ if (!withServiceSocietaux && category === 'services sociétaux') { return acc } - return acc.concat(subcategories[category]) + return acc.concat(subcategories[category as DottedName]) }, [] as DottedName[]), [subcategories, withServiceSocietaux] ) - const sortedSubcategories = useMemo<DottedName[]>(() => { + const sortedSubcategories = useMemo(() => { return everySubcategories.sort( (categoryA: DottedName, categoryB: DottedName) => { const valueA = getNumericValue(categoryA) ?? 0 diff --git a/src/hooks/useSortedUiCategoriesByFootprint.ts b/src/hooks/useSortedUiCategoriesByFootprint.ts index d63d5a3e1..262a49c45 100644 --- a/src/hooks/useSortedUiCategoriesByFootprint.ts +++ b/src/hooks/useSortedUiCategoriesByFootprint.ts @@ -1,6 +1,7 @@ import { eauMetric } from '@/constants/metric' import { useEngine, useSimulation } from '@/publicodes-state' -import { DottedName, Metric } from '@/publicodes-state/types' +import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' type Props = { @@ -14,7 +15,7 @@ export function useSortedUiCategoriesByFootprint({ metric }: Props = {}) { const everyUiCategoriesWithRepasAjusted = useMemo( () => metric === eauMetric - ? [ + ? ([ ...everyUiCategories.filter( (category) => !['viande', 'végé', 'poisson'].some((repasDottedName) => @@ -22,12 +23,12 @@ export function useSortedUiCategoriesByFootprint({ metric }: Props = {}) { ) ), 'alimentation . déjeuner et dîner', - ] - : (everyUiCategories as DottedName[]), + ] as DottedName[]) + : everyUiCategories, [everyUiCategories, metric] ) - const sortedUiCategories = useMemo<DottedName[]>(() => { + const sortedUiCategories = useMemo(() => { return everyUiCategoriesWithRepasAjusted.sort( (categoryA: DottedName, categoryB: DottedName) => { const valueA = getNumericValue(categoryA) ?? 0 @@ -35,7 +36,7 @@ export function useSortedUiCategoriesByFootprint({ metric }: Props = {}) { return valueB - valueA } - ) + ) as DottedName[] }, [everyUiCategoriesWithRepasAjusted, getNumericValue]) return { diff --git a/src/locales/ui/ui-en.yaml b/src/locales/ui/ui-en.yaml index 4d4941833..9656da3b0 100644 --- a/src/locales/ui/ui-en.yaml +++ b/src/locales/ui/ui-en.yaml @@ -1124,3 +1124,29 @@ entries: Mon empreinte carbone: My carbon footprint Mon empreinte eau: My water footprint Et l'eau de <2>ma douche</2> dans tout ça ?: What about the water in <2>my shower</2>? + sur votre empreinte: on your footprint + Reprendre plus tard: Resume later + Sauvegarder <2>ma progression</2>: Save <2>my progress</2> + d'eau par jour: of water per day + Votre région a bien été mise à jour.: Your region has been updated. + Vous pouvez la reprendre plus tard en cliquant sur le lien que vous avez reçu par email.: You can resume it later by clicking on the link you received by email. + de CO₂e par an: of CO₂e per year + Recevez par email un lien pour reprendre votre test plus tard.: Receive an email with a link to take your test again later. + Liste des questions: List of questions + Passer la question: Skip the question + Une erreur s'est produite au moment de la sauvegarde.: An error has occurred during backup. + Veuillez entrer un nombre positif.: Please enter a positive number. + '{{signe}} {{value}} {{unit}} sur votre empreinte': '{{signe}} {{value}} {{unit}} on your footprint' + '{{name}}, supprimer cette participation': '{{name}}cancel this shareholding' + Supprimer ce participant ? Cette opération est définitive.: Delete this participant? This operation is definitive. + Participant supprimé avec succès: Participant successfully deleted + Une erreur est survenue: An error has occurred + Des enfants qui se tiennent la main: Children holding hands + Une fille qui cuisine: A girl who cooks + Une mère et son enfant sur un vélo: A mother and child on a bicycle + Une fille qui lit un journal: A girl reading a newspaper + Non, merci: No, thank you + Vous pouvez le reprendre plus tard en cliquant sur le lien que vous avez reçu par email.: You can resume it later by clicking on the link you received by email. + Un grand-père et sa petite fille qui regardent un film: A grandfather and his granddaughter watching a film + Votre test est sauvegardé !: Your test is saved! + <0>La taille des logements français a très fortement augmenté</0> sur ces 50 dernirèes années, passant de 23 à 40,4 m² par habitant, soit <4>90,9 m² en moyenne pour un foyer de 2,2 personnes.</4>: <0>The size of French homes has risen sharply</0> over the last 50 years, from 23 to 40.4 m² per inhabitant, or <4>90.9 m² on average for a household of 2.2 people.</4> diff --git a/src/locales/ui/ui-es.yaml b/src/locales/ui/ui-es.yaml index 8baead26b..179bb5ba5 100644 --- a/src/locales/ui/ui-es.yaml +++ b/src/locales/ui/ui-es.yaml @@ -1117,3 +1117,29 @@ entries: Et l'eau de <2>ma douche</2> dans tout ça ?: ¿Qué pasa con el agua de <2>mi ducha</2>? Mon empreinte carbone: Mi huella de carbono Mon empreinte eau: Mi huella hídrica + sur votre empreinte: sobre su huella + Passer la question: Saltar la pregunta + Votre région a bien été mise à jour.: Su región ha sido actualizada. + Une erreur s'est produite au moment de la sauvegarde.: Se ha producido un error durante la copia de seguridad. + Vous pouvez la reprendre plus tard en cliquant sur le lien que vous avez reçu par email.: Puede reanudarla más tarde haciendo clic en el enlace que recibió por correo electrónico. + Liste des questions: Lista de preguntas + Sauvegarder <2>ma progression</2>: Guardar <2>mis progresos</2> + d'eau par jour: de agua al día + Recevez par email un lien pour reprendre votre test plus tard.: Reciba un correo electrónico con un enlace para volver a realizar la prueba más tarde. + de CO₂e par an: de CO₂e al año + Reprendre plus tard: Reanudar más tarde + Veuillez entrer un nombre positif.: Introduzca un número positivo. + '{{signe}} {{value}} {{unit}} sur votre empreinte': '{{signe}} {{value}} {{unit}} sobre su huella' + Une erreur est survenue: Se ha producido un error + '{{name}}, supprimer cette participation': '{{name}}cancelar esta participación' + Participant supprimé avec succès: Participante eliminado con éxito + Supprimer ce participant ? Cette opération est définitive.: ¿Borrar a este participante? Esta operación es definitiva. + Non, merci: No, gracias + Une fille qui cuisine: Una chica que cocina + Une fille qui lit un journal: Una chica leyendo el periódico + Votre test est sauvegardé !: Tu examen está guardado + Des enfants qui se tiennent la main: Niños cogidos de la mano + Vous pouvez le reprendre plus tard en cliquant sur le lien que vous avez reçu par email.: Puede reanudarla más tarde haciendo clic en el enlace que recibió por correo electrónico. + Une mère et son enfant sur un vélo: Una madre y su hijo en bicicleta + Un grand-père et sa petite fille qui regardent un film: Un abuelo y su nieta viendo una película + <0>La taille des logements français a très fortement augmenté</0> sur ces 50 dernirèes années, passant de 23 à 40,4 m² par habitant, soit <4>90,9 m² en moyenne pour un foyer de 2,2 personnes.</4>: <0>El tamaño de las viviendas francesas ha aumentado mucho</0> en los últimos 50 años, pasando de 23 a 40,4 m² por habitante, es decir, <4>90,9 m² de media para un hogar de 2,2 personas.</4> diff --git a/src/locales/ui/ui-fr.yaml b/src/locales/ui/ui-fr.yaml index d89da6642..43cca0797 100644 --- a/src/locales/ui/ui-fr.yaml +++ b/src/locales/ui/ui-fr.yaml @@ -30,7 +30,6 @@ entries: Fermer: Fermer Fermer les options de tri: Fermer les options de tri J'ai compris: J'ai compris - Je ne sais pas: Je ne sais pas Le modèle d'empreinte carbone de référence: Le modèle d'empreinte carbone de référence Le test: Le test Le titre bref de votre problème: Le titre bref de votre problème @@ -206,14 +205,10 @@ entries: Bientôt disponible: Bientôt disponible C'est parti ! →: C'est parti ! → C'est un test individuel !: C'est un test individuel ! - C’est quoi mon empreinte carbone ?: C’est quoi mon empreinte carbone ? Calcul d'empreinte carbone manquant: Calcul d'empreinte carbone manquant - chrono pour calculer votre empreinte sur le climat: chrono pour calculer votre empreinte sur le climat - Comment on la mesure ?: Comment on la mesure ? Comparez vos résultats avec votre famille ou un groupe d’ami·e·s: Comparez vos résultats avec votre famille ou un groupe d’ami·e·s Comprendre mon score: Comprendre mon score Consultez la FAQ: Consultez la FAQ - D’où vient mon empreinte ?: D’où vient mon empreinte ? 'Date complète :': 'Date complète :' 'Découvrez nos articles de blog :': 'Découvrez nos articles de blog :' disponibles: disponibles @@ -243,7 +238,6 @@ entries: Sélectionner: Sélectionner Supprimer: Supprimer Switch to english: Switch to english - Voir la liste des questions: Voir la liste des questions Voir plus d'informations: Voir plus d'informations Vous: Vous 'Vous faites cette simulation depuis :': 'Vous faites cette simulation depuis :' @@ -251,9 +245,6 @@ entries: Vous utilisez la version: Vous utilisez la version Terminer: Terminer Voir toutes les actions: Voir toutes les actions - Total.unit: de CO<1>2</1>e / an - Comparez vos résultats <2>avec vos proches</2>: Comparez vos résultats <2>avec vos proches</2> - Créer un groupe: Créer un groupe Distance: Distance Fréquence: Fréquence La catégorie {{title}} représente {{formattedValue}} tonnes de CO2 equivalent.: La catégorie {{title}} représente {{formattedValue}} tonnes de CO2 equivalent. @@ -261,7 +252,6 @@ entries: Passagers: Passagers Télécharger l'image: Télécharger l'image 'Total par an :': 'Total par an :' - Une capture du mode Groupe Nos Gestes Climat.: Une capture du mode Groupe Nos Gestes Climat. Merci pour votre retour !: Merci pour votre retour ! 'Total :': 'Total :' ou: ou @@ -398,7 +388,6 @@ entries: L'empreinte climat, qu'est-ce que c'est ?: L'empreinte climat, qu'est-ce que c'est ? L'intégralité du: L'intégralité du Le: Le - obtenez une estimation de votre empreinte carbone de consommation.: obtenez une estimation de votre empreinte carbone de consommation. pour les curieux et les experts.: pour les curieux et les experts. Toute contribution est la bienvenue !: Toute contribution est la bienvenue ! Une capture du mode Amis Nos Gestes Climat.: Une capture du mode Amis Nos Gestes Climat. @@ -625,7 +614,6 @@ entries: 'Depuis le siècle dernier, <2>la concentration du carbone dans l’atmosphère augmente</2> tant et si bien que le climat de la planète subit des bouleversements aux lourdes conséquences : montée des eaux, destruction du vivant, explosion des catastrophes climatiques.': 'Depuis le siècle dernier, <2>la concentration du carbone dans l’atmosphère augmente</2> tant et si bien que le climat de la planète subit des bouleversements aux lourdes conséquences : montée des eaux, destruction du vivant, explosion des catastrophes climatiques.' Le consensus scientifique est formel, cette augmentation est <2>directement liée aux activités humaines :</2> l’extraction, la consommation et la combustion de ressources dépassent les capacités d’absorption de notre planète. <6>Il est grand temps de réduire ou remplacer</6> ces activités émettrices de gaz à effet de serre, à toutes les échelles !: Le consensus scientifique est formel, cette augmentation est <2>directement liée aux activités humaines :</2> l’extraction, la consommation et la combustion de ressources dépassent les capacités d’absorption de notre planète. <6>Il est grand temps de réduire ou remplacer</6> ces activités émettrices de gaz à effet de serre, à toutes les échelles ! Une erreur est survenue !: Une erreur est survenue ! - '{formatCarbonFootprint(carbonFootprintValue).unit}': '{formatCarbonFootprint(carbonFootprintValue).unit}' '<0>Infolettre saisonnière de Nos Gestes Climat</0> : actualités climat, initiatives positives et nouveautés': '<0>Infolettre saisonnière de Nos Gestes Climat</0> : actualités climat, initiatives positives et nouveautés' "<0>Nos Gestes Transports</0> : tout savoir ou presque sur l'impact carbone des transports, en 4 e-mails": "<0>Nos Gestes Transports</0> : tout savoir ou presque sur l'impact carbone des transports, en 4 e-mails" Ce champ est requis.: Ce champ est requis. @@ -658,14 +646,11 @@ entries: secondes): secondes) Vous n'avez pas reçu de code ?: Vous n'avez pas reçu de code ? <0>2 tonnes</0> en 2050 ?: <0>2 tonnes</0> en 2050 ? - <0>Et vous,</0> quel est votre impact sur le climat ?: <0>Et vous,</0> quel est votre impact sur le climat ? <0>Hérissons</0> en danger ?: <0>Hérissons</0> en danger ? C’est l’objectif à atteindre pour espérer limiter le réchauffement climatique à 2 degrés.: C’est l’objectif à atteindre pour espérer limiter le réchauffement climatique à 2 degrés. Comment est calculée votre empreinte ?: Comment est calculée votre empreinte ? De plus, la disparition de leur environnement naturel, due à la déforestation et à l'urbanisation, réduit leurs zones de vie potentielles, augmentant ainsi leur vulnérabilité face aux prédateurs et aux maladies.: De plus, la disparition de leur environnement naturel, due à la déforestation et à l'urbanisation, réduit leurs zones de vie potentielles, augmentant ainsi leur vulnérabilité face aux prédateurs et aux maladies. Découvre mon bilan carbone sur Nos Gestes Climat.: Découvre mon bilan carbone sur Nos Gestes Climat. - Découvre mon empreinte carbone: Découvre mon empreinte carbone - En 10 minutes, obtenez une estimation de votre empreinte carbone.: En 10 minutes, obtenez une estimation de votre empreinte carbone. Est-ce que je peux y arriver tout seul ?: Est-ce que je peux y arriver tout seul ? l’objectif pour 2050: l’objectif pour 2050 Le détail de mon empreinte: Le détail de mon empreinte @@ -679,7 +664,6 @@ entries: 'Recevez des conseils pour réduire votre empreinte :': 'Recevez des conseils pour réduire votre empreinte :' 'Voici quelques idées pour vous aider à réduire votre impact :': 'Voici quelques idées pour vous aider à réduire votre impact :' Vous êtes très nettement en dessous de la moyenne française.: Vous êtes très nettement en dessous de la moyenne française. - Vous souhaitez recevoir vos résultats d’empreinte carbone ?: Vous souhaitez recevoir vos résultats d’empreinte carbone ? ' de votre empreinte': ' de votre empreinte' 2 tonnes: 2 tonnes Ajouter la question: Ajouter la question @@ -871,7 +855,6 @@ entries: Vous devez valider votre changement d'adresse e-mail.: Vous devez valider votre changement d'adresse e-mail. Recevoir nos actualités sur les nouveaux services dédiés aux organisation (une fois par mois maximum !): Recevoir nos actualités sur les nouveaux services dédiés aux organisation (une fois par mois maximum !) <0>Eau, climat,</0> même combat ?: <0>Eau, climat,</0> même combat ? - <0>En règle générale, les valeurs d'empreinte eau varient <2>entre 9000 et 15000 litres par jour</2>. Contrairement au carbone, il n'existe pas d'objectif chiffré pour l'empreinte eau.</0><1>Nous n'affichons pas la valeur par défaut en début de test. En effet, notre modèle eau étant le premier du genre en France, nous ne voulons pas qu'elle soit perçue comme la valeur moyenne nationale.</1>: <0>En règle générale, les valeurs d'empreinte eau varient <2>entre 9000 et 15000 litres par jour</2>. Contrairement au carbone, il n'existe pas d'objectif chiffré pour l'empreinte eau.</0><1>Nous n'affichons pas la valeur par défaut en début de test. En effet, notre modèle eau étant le premier du genre en France, nous ne voulons pas qu'elle soit perçue comme la valeur moyenne nationale.</1> <0>Et vous,</0> quel est votre impact sur notre planète ?: <0>Et vous,</0> quel est votre impact sur notre planète ? <0>L’empreinte eau,</0> c’est quoi ?: <0>L’empreinte eau,</0> c’est quoi ? <0>La majeure partie de l’empreinte eau concerne la pousse des végétaux,</0> que ce soit pour nous alimenter, pour nourrir le bétail, ou pour obtenir la matière première de nombre de nos vêtements.: <0>La majeure partie de l’empreinte eau concerne la pousse des végétaux,</0> que ce soit pour nous alimenter, pour nourrir le bétail, ou pour obtenir la matière première de nombre de nos vêtements. @@ -879,7 +862,6 @@ entries: Bientôt disponible !: Bientôt disponible ! C’est quoi mon empreinte carbone ?: C’est quoi mon empreinte carbone ? carbone: carbone - Ce chiffre vous semble impressionnant ? <1></1>C'est pourtant bien l'eau qui sert à produire ce que vous consommez :<3></3> votre empreinte eau, c'est l'impact de votre mode de vie sur les cycles naturels de l'eau.: Ce chiffre vous semble impressionnant ? <1></1>C'est pourtant bien l'eau qui sert à produire ce que vous consommez :<3></3> votre empreinte eau, c'est l'impact de votre mode de vie sur les cycles naturels de l'eau. chrono pour calculer votre empreinte carbone et eau: chrono pour calculer votre empreinte carbone et eau Comment <1>agir</1> ?: Comment <1>agir</1> ? Comment on la mesure ?: Comment on la mesure ? @@ -891,7 +873,6 @@ entries: eau: eau En 10 minutes, obtenez une estimation de vos empreintes carbone et eau.: En 10 minutes, obtenez une estimation de vos empreintes carbone et eau. Est-ce que c'est <2>beaucoup ?</2>: Est-ce que c'est <2>beaucoup ?</2> - Et l'eau de <2>ma douche</2> <4></4>dans tout ça ?: Et l'eau de <2>ma douche</2> <4></4>dans tout ça ? Evaluer la métrique carbone: Evaluer la métrique carbone Evaluer la métrique eau: Evaluer la métrique eau l’alimentation des <2>animaux</2> que vous consommez: l’alimentation des <2>animaux</2> que vous consommez @@ -906,7 +887,6 @@ entries: les <2>fruits, légumes et céréales</2> que vous mangez: les <2>fruits, légumes et céréales</2> que vous mangez Les 3 réflexes à adopter pour une garde-robe économe en eau: Les 3 réflexes à adopter pour une garde-robe économe en eau Les 4 gestes pour réduire l’empreinte eau de mon alimentation: Les 4 gestes pour réduire l’empreinte eau de mon alimentation - 'Les activités humaines menacent les équilibres naturels de notre planète. Comme le climat, <2>le cycle de l’eau est fortement impacté partout dans le monde</2>: accès à l’eau potable, disparition et migration d’espèces (animales et végétales), déplacement de populations.': 'Les activités humaines menacent les équilibres naturels de notre planète. Comme le climat, <2>le cycle de l’eau est fortement impacté partout dans le monde</2>: accès à l’eau potable, disparition et migration d’espèces (animales et végétales), déplacement de populations.' Mes empreintes: Mes empreintes Mes principaux postes d'usage: Mes principaux postes d'usage 'Nos articles sur le sujet :': 'Nos articles sur le sujet :' @@ -926,10 +906,34 @@ entries: Les activités humaines menacent les équilibres naturels de notre planète.: Les activités humaines menacent les équilibres naturels de notre planète. C'est pourtant bien l'eau qui sert à produire ce que vous consommez :<1></1> votre empreinte eau, c'est l'impact de votre mode de vie sur les cycles naturels de l'eau.: C'est pourtant bien l'eau qui sert à produire ce que vous consommez :<1></1> votre empreinte eau, c'est l'impact de votre mode de vie sur les cycles naturels de l'eau. Ce chiffre vous semble impressionnant ?: Ce chiffre vous semble impressionnant ? - '*La moyenne française est de {average} litres par jour': '*La moyenne française est de {average} litres par jour' '*La moyenne française est de': '*La moyenne française est de' 'L’empreinte eau : pourquoi et comment avons-nous travaillé…': 'L’empreinte eau : pourquoi et comment avons-nous travaillé…' litres par jour: litres par jour Et l'eau de <2>ma douche</2> dans tout ça ?: Et l'eau de <2>ma douche</2> dans tout ça ? Mon empreinte carbone: Mon empreinte carbone Mon empreinte eau: Mon empreinte eau + d'eau par jour: d'eau par jour + de CO₂e par an: de CO₂e par an + Liste des questions: Liste des questions + Passer la question: Passer la question + Recevez par email un lien pour reprendre votre test plus tard.: Recevez par email un lien pour reprendre votre test plus tard. + Reprendre plus tard: Reprendre plus tard + Sauvegarder <2>ma progression</2>: Sauvegarder <2>ma progression</2> + sur votre empreinte: sur votre empreinte + Une erreur s'est produite au moment de la sauvegarde.: Une erreur s'est produite au moment de la sauvegarde. + Votre région a bien été mise à jour.: Votre région a bien été mise à jour. + Veuillez entrer un nombre positif.: Veuillez entrer un nombre positif. + '{{signe}} {{value}} {{unit}} sur votre empreinte': '{{signe}} {{value}} {{unit}} sur votre empreinte' + '{{name}}, supprimer cette participation': '{{name}}, supprimer cette participation' + Participant supprimé avec succès: Participant supprimé avec succès + Supprimer ce participant ? Cette opération est définitive.: Supprimer ce participant ? Cette opération est définitive. + Une erreur est survenue: Une erreur est survenue + <0>La taille des logements français a très fortement augmenté</0> sur ces 50 dernirèes années, passant de 23 à 40,4 m² par habitant, soit <4>90,9 m² en moyenne pour un foyer de 2,2 personnes.</4>: <0>La taille des logements français a très fortement augmenté</0> sur ces 50 dernirèes années, passant de 23 à 40,4 m² par habitant, soit <4>90,9 m² en moyenne pour un foyer de 2,2 personnes.</4> + Des enfants qui se tiennent la main: Des enfants qui se tiennent la main + Non, merci: Non, merci + Un grand-père et sa petite fille qui regardent un film: Un grand-père et sa petite fille qui regardent un film + Une fille qui cuisine: Une fille qui cuisine + Une fille qui lit un journal: Une fille qui lit un journal + Une mère et son enfant sur un vélo: Une mère et son enfant sur un vélo + Votre test est sauvegardé !: Votre test est sauvegardé ! + Vous pouvez le reprendre plus tard en cliquant sur le lien que vous avez reçu par email.: Vous pouvez le reprendre plus tard en cliquant sur le lien que vous avez reçu par email. diff --git a/src/middleware.ts b/src/middleware.ts index 0648a4417..d77cce80a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -36,23 +36,23 @@ function isI18n(response: NextResponse): boolean { /** * Evite que le middleware soit appliqué à certaines routes */ -export const config = { - matcher: [ - /* - * Match all request paths except for the ones starting with: - * - api (API routes) - * - _next/static (static files) - * - images (image optimization files) - * - favicon.ico (favicon file) - * - manifest.webmanifest (manifest file) - */ - { - source: - '/((?!api|_next/static|favicon.ico|images|manifest.webmanifest).*)', - missing: [ - { type: 'header', key: 'next-router-prefetch' }, - { type: 'header', key: 'purpose', value: 'prefetch' }, - ], - }, - ], -} +// export const config = { +// matcher: [ +// /* +// * Match all request paths except for the ones starting with: +// * - api (API routes) +// * - _next/static (static files) +// * - images (image optimization files) +// * - favicon.ico (favicon file) +// * - manifest.webmanifest (manifest file) +// */ +// { +// source: +// '/((?!api|_next/static|favicon.ico|images|manifest.webmanifest).*)', +// missing: [ +// { type: 'header', key: 'next-router-prefetch' }, +// { type: 'header', key: 'purpose', value: 'prefetch' }, +// ], +// }, +// ], +// } diff --git a/src/publicodes-state/__mocks__/index.ts b/src/publicodes-state/__mocks__/index.ts new file mode 100644 index 000000000..a828b15ec --- /dev/null +++ b/src/publicodes-state/__mocks__/index.ts @@ -0,0 +1,32 @@ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import rules from '@incubateur-ademe/nosgestesclimat/public/co2-model.FR-lang.fr.json' +import Engine from 'publicodes' +import { Situation } from '../types' + +const engine = new Engine(rules) + +export const useTempEngine = jest.fn(() => { + return { + rules, + getSpecialRuleObject: engine.getRule.bind(engine), + } +}) + +export const useDisposableEngine = jest.fn(() => { + const engineEvaluate = engine.evaluate.bind(engine) + const engineSetSituation = engine.setSituation.bind(engine) + + return { + getValue: (dottedName: DottedName) => { + try { + return engineEvaluate(dottedName).nodeValue + } catch (e) { + return null + } + }, + updateSituation: (newSituation: Situation) => + engineSetSituation(newSituation, { + keepPreviousSituation: true, + }), + } +}) diff --git a/src/publicodes-state/helpers/getDisposableEngine.ts b/src/publicodes-state/helpers/getDisposableEngine.ts index 89aee8a6f..5d6a0f04e 100644 --- a/src/publicodes-state/helpers/getDisposableEngine.ts +++ b/src/publicodes-state/helpers/getDisposableEngine.ts @@ -1,23 +1,23 @@ -import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRules } from '@incubateur-ademe/nosgestesclimat' import Engine from 'publicodes' -import { Rules, Situation } from '../types' +import { Situation } from '../types' import { safeEvaluateHelper } from './safeEvaluateHelper' type Props = { - rules?: Rules + rules?: NGCRules situation: Situation } // Helper version of the useDisposableEngine hook, usable in a loop export function getDisposableEngine({ rules, situation }: Props) { - const engine = new Engine(rules, { + const engine = new Engine<DottedName>(rules, { strict: { situation: false, noOrphanRule: false, }, }).setSituation(situation) - const safeEvaluate = (rule: string) => safeEvaluateHelper(rule, engine) + const safeEvaluate = (rule: DottedName) => safeEvaluateHelper(rule, engine) const getValue = (dottedName: DottedName) => safeEvaluate(dottedName)?.nodeValue diff --git a/src/publicodes-state/helpers/getIsMissing.ts b/src/publicodes-state/helpers/getIsMissing.ts index 44d2bfcfd..769881917 100644 --- a/src/publicodes-state/helpers/getIsMissing.ts +++ b/src/publicodes-state/helpers/getIsMissing.ts @@ -1,9 +1,10 @@ -import { DottedName, Situation } from '../types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { Situation } from '../types' type Props = { dottedName: DottedName situation: Situation - questionsOfMosaicFromParent?: string[] + questionsOfMosaicFromParent?: DottedName[] } export default function getIsMissing({ diff --git a/src/publicodes-state/helpers/getNamespace.ts b/src/publicodes-state/helpers/getNamespace.ts index 9734d4ac4..a692620d6 100644 --- a/src/publicodes-state/helpers/getNamespace.ts +++ b/src/publicodes-state/helpers/getNamespace.ts @@ -1,5 +1,4 @@ -import { DottedName } from '@/publicodes-state/types' - +import { DottedName } from '@incubateur-ademe/nosgestesclimat' /** * Returns the namespace of a rule. A level can be specified to get a specific * part of the namespace. @@ -18,7 +17,7 @@ import { DottedName } from '@/publicodes-state/types' export default function getNamespace( rule: DottedName | undefined | null, level: number = 1 -): string | undefined { +): DottedName | undefined { if (rule === undefined || rule === null) { return undefined } @@ -28,9 +27,9 @@ export default function getNamespace( return rule } if (level === 1) { - return splittedRule[0] + return splittedRule[0] as DottedName } if (level > 1) { - return splittedRule.slice(0, level).join(' . ') + return splittedRule.slice(0, level).join(' . ') as DottedName } } diff --git a/src/publicodes-state/helpers/getSomme.ts b/src/publicodes-state/helpers/getSomme.ts index 82548a970..cffeda08d 100644 --- a/src/publicodes-state/helpers/getSomme.ts +++ b/src/publicodes-state/helpers/getSomme.ts @@ -1,4 +1,4 @@ -import { NGCRule } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRule } from '@incubateur-ademe/nosgestesclimat' /** * We use this hook to get the content of the [somme] of a rule. @@ -8,7 +8,7 @@ import { NGCRule } from '@incubateur-ademe/nosgestesclimat' * at the root of the rule and not in a [formule] mechanism (both syntaxes are valid). * * With the new `eau` metric, for some categories, the `somme` is not in the `formule` only but in a `variations` mechanism like: - * + * [ { "si": "métrique = 'carbone'", @@ -35,21 +35,22 @@ import { NGCRule } from '@incubateur-ademe/nosgestesclimat' type subCatWithVariations = Array<{ si: string alors: { - somme: string[] + somme: DottedName[] } }> -export default function getSomme(rawNode?: NGCRule): string[] | undefined { +export default function getSomme(rawNode?: NGCRule): DottedName[] | undefined { if (!rawNode) return undefined if ('formule' in rawNode) { - return rawNode.formule?.variations - ? (rawNode.formule?.variations as subCatWithVariations)[0]?.alors?.somme - : (rawNode.formule?.somme as string[]) + const formule = rawNode.formule as Record<string, unknown> + return 'variations' in formule + ? (formule.variations as subCatWithVariations)[0]?.alors?.somme + : (formule.somme as DottedName[]) } if ('somme' in rawNode) { - return rawNode.somme as string[] + return rawNode.somme as DottedName[] } if ('variations' in rawNode) { diff --git a/src/publicodes-state/helpers/getSortedQuestionsList.ts b/src/publicodes-state/helpers/getSortedQuestionsList.ts index 17c996119..f8d34847c 100644 --- a/src/publicodes-state/helpers/getSortedQuestionsList.ts +++ b/src/publicodes-state/helpers/getSortedQuestionsList.ts @@ -1,4 +1,4 @@ -import { DottedName } from '../types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { questions: DottedName[] diff --git a/src/publicodes-state/helpers/getType.ts b/src/publicodes-state/helpers/getType.ts index 6c902db99..366fd0b0e 100644 --- a/src/publicodes-state/helpers/getType.ts +++ b/src/publicodes-state/helpers/getType.ts @@ -1,4 +1,5 @@ -import { DottedName, NGCEvaluatedNode, NGCRuleNode } from '../types' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' +import { NGCEvaluatedNode } from '../types' type Props = { dottedName: DottedName diff --git a/src/publicodes-state/helpers/migrateSimulation.ts b/src/publicodes-state/helpers/migrateSimulation.ts index 209d2578f..43cff96c5 100644 --- a/src/publicodes-state/helpers/migrateSimulation.ts +++ b/src/publicodes-state/helpers/migrateSimulation.ts @@ -1,3 +1,4 @@ +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { Migration, migrateSituation } from '@publicodes/tools/migration' import { Simulation } from '../types' @@ -24,7 +25,7 @@ export function migrateSimulation( ), migrationInstructions ) - ) + ) as DottedName[] } // If group or poll is defined, we convert it to groups or polls and delete it @@ -44,7 +45,7 @@ export function migrateSimulation( carbone: simulation.computedResults as any, eau: { bilan: 0, - categories: {}, + categories: {} as Record<DottedName, number>, }, } simulation.computedResults = newComputedResults diff --git a/src/publicodes-state/helpers/safeEvaluateHelper.ts b/src/publicodes-state/helpers/safeEvaluateHelper.ts index e05b82b24..e1e347abf 100644 --- a/src/publicodes-state/helpers/safeEvaluateHelper.ts +++ b/src/publicodes-state/helpers/safeEvaluateHelper.ts @@ -1,12 +1,12 @@ +import { Engine, NGCEvaluatedNode } from '@/publicodes-state/types' import { captureException } from '@sentry/react' -import Engine, { PublicodesExpression } from 'publicodes' -import { NGCEvaluatedNode } from '../types' +import { PublicodesExpression } from 'publicodes' export const safeEvaluateHelper = ( expr: PublicodesExpression, engineUsed: Engine ): NGCEvaluatedNode | null => { - let evaluation = null + let evaluation: NGCEvaluatedNode | null = null try { evaluation = engineUsed.evaluate(expr) } catch (error) { diff --git a/src/publicodes-state/helpers/safeGetRuleHelper.ts b/src/publicodes-state/helpers/safeGetRuleHelper.ts index 199aa58d1..498eccc7d 100644 --- a/src/publicodes-state/helpers/safeGetRuleHelper.ts +++ b/src/publicodes-state/helpers/safeGetRuleHelper.ts @@ -1,6 +1,6 @@ +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { captureException } from '@sentry/react' -import Engine from 'publicodes' -import { DottedName, NGCRuleNode } from '../types' +import { Engine } from '../types' export const safeGetRuleHelper = ( ruleName: DottedName, diff --git a/src/publicodes-state/hooks/useActions/index.ts b/src/publicodes-state/hooks/useActions/index.ts index 770ac63d1..488e9096e 100644 --- a/src/publicodes-state/hooks/useActions/index.ts +++ b/src/publicodes-state/hooks/useActions/index.ts @@ -2,7 +2,8 @@ import { carboneMetric } from '@/constants/metric' import getSomme from '@/publicodes-state/helpers/getSomme' -import { DottedName, Metric } from '@/publicodes-state/types' +import { Metric } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useContext, useMemo } from 'react' import { useEngine } from '../..' import { SimulationContext } from '../../providers/simulationProvider/context' @@ -43,9 +44,9 @@ export default function useActions( return somme }, [engine]) - const orderedActions = useMemo<string[]>(() => { + const orderedActions = useMemo(() => { return actions - .map((action: string) => ({ + .map((action) => ({ dottedName: action, value: getNumericValue(action), })) @@ -54,7 +55,7 @@ export default function useActions( }, [actions, getNumericValue]) const { chosenActions, declinedActions } = - Object.keys(actionChoices ?? {})?.reduce( + Object.keys(actionChoices ?? {}).reduce( (accActions, currentAction) => { const actionChoice = actionChoices[currentAction] @@ -80,10 +81,20 @@ export default function useActions( } ) || 0 + const totalChosenActionsValue: number = useMemo( + () => + chosenActions.reduce( + (acc, action) => acc + getNumericValue(action as DottedName), + 0 + ), + [chosenActions, getNumericValue] + ) + return { actions, orderedActions, chosenActions, declinedActions, + totalChosenActionsValue, } } diff --git a/src/publicodes-state/hooks/useDisposableEngine/index.ts b/src/publicodes-state/hooks/useDisposableEngine/index.ts index eb84aa9e2..6ad69b283 100644 --- a/src/publicodes-state/hooks/useDisposableEngine/index.ts +++ b/src/publicodes-state/hooks/useDisposableEngine/index.ts @@ -1,8 +1,9 @@ import { SimulationContext } from '@/publicodes-state/providers/simulationProvider/context' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import Engine from 'publicodes' import { useContext, useMemo } from 'react' import { safeEvaluateHelper } from '../../helpers/safeEvaluateHelper' -import { DottedName, Situation } from '../../types' +import { Situation } from '../../types' type Props = { rules?: any @@ -17,7 +18,7 @@ export default function useDisposableEngine({ rules, situation }: Props) { const { rules: contextRules } = useContext(SimulationContext) const engine = useMemo(() => { - return new Engine(rules ?? contextRules, { + return new Engine<DottedName>(rules ?? contextRules, { logger: { warn: () => {}, error: () => {}, log: () => {} }, strict: { situation: false, diff --git a/src/publicodes-state/hooks/useEngine/index.ts b/src/publicodes-state/hooks/useEngine/index.ts index f5e0cfac9..85c0df489 100644 --- a/src/publicodes-state/hooks/useEngine/index.ts +++ b/src/publicodes-state/hooks/useEngine/index.ts @@ -1,7 +1,9 @@ import getNamespace from '@/publicodes-state/helpers/getNamespace' +import { DottedName, NodeValue } from '@incubateur-ademe/nosgestesclimat' +import { utils } from 'publicodes' import { useCallback, useContext } from 'react' import { SimulationContext } from '../../providers/simulationProvider/context' -import { DottedName, Metric, NodeValue } from '../../types' +import { Metric } from '../../types' /** * A hook that make available some basic functions on the engine (and the engine itself). @@ -16,6 +18,7 @@ export default function useEngine({ metric }: Props = {}) { engine, safeEvaluate: safeEvaluate, safeGetRule, + parsedRules, } = useContext(SimulationContext) const getValue = (dottedName: DottedName): NodeValue => @@ -29,11 +32,29 @@ export default function useEngine({ metric }: Props = {}) { [safeEvaluate, metric] ) - const getCategory = (dottedName: DottedName): string => - getNamespace(dottedName, 1) ?? '' + const getCategory = (dottedName: DottedName): DottedName => + getNamespace(dottedName, 1) ?? ('' as DottedName) - const getSubcategories = (dottedName: DottedName): string[] => - safeGetRule(dottedName)?.rawNode?.formule?.somme + const getSubcategories = (dottedName: DottedName): DottedName[] => { + // TO FIX: The `somme` cannot be in a formula. + const dottedNameFormula = safeGetRule(dottedName)?.rawNode?.formule + + if ( + !dottedNameFormula || + typeof dottedNameFormula === 'string' || + !Array.isArray(dottedNameFormula.somme) + ) { + return [] + } + + return dottedNameFormula.somme.map((potentialPartialRuleName: DottedName) => + utils.disambiguateReference( + parsedRules, + dottedName, + potentialPartialRuleName + ) + ) + } const checkIfValid = (dottedName: DottedName): boolean => safeGetRule(dottedName) ? true : false diff --git a/src/publicodes-state/hooks/useForm/index.ts b/src/publicodes-state/hooks/useForm/index.ts index e57c67839..06d20d327 100644 --- a/src/publicodes-state/hooks/useForm/index.ts +++ b/src/publicodes-state/hooks/useForm/index.ts @@ -9,6 +9,7 @@ import useNavigation from './useNavigation' */ export default function useForm() { const { + questionsByCategories, relevantQuestions, currentQuestion, currentCategory, @@ -34,6 +35,10 @@ export default function useForm() { }) return { + /** + * Every questions sorted by category + */ + questionsByCategories, /** * Every questions (answered and missing) that should be displayed in the form */ diff --git a/src/publicodes-state/hooks/useForm/useNavigation.ts b/src/publicodes-state/hooks/useForm/useNavigation.ts index b9f96a8e1..755737a7e 100644 --- a/src/publicodes-state/hooks/useForm/useNavigation.ts +++ b/src/publicodes-state/hooks/useForm/useNavigation.ts @@ -1,11 +1,12 @@ import getNamespace from '@/publicodes-state/helpers/getNamespace' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' type Props = { - remainingQuestions: string[] - relevantQuestions: string[] - currentQuestion: string | null - setCurrentQuestion: (question: string | null) => void + remainingQuestions: DottedName[] + relevantQuestions: DottedName[] + currentQuestion: DottedName | null + setCurrentQuestion: (question: DottedName | null) => void } export default function useNavigation({ @@ -14,7 +15,7 @@ export default function useNavigation({ currentQuestion, setCurrentQuestion, }: Props) { - const currentQuestionNamespace = useMemo<string | undefined>( + const currentQuestionNamespace = useMemo( () => getNamespace(currentQuestion), [currentQuestion] ) @@ -50,7 +51,7 @@ export default function useNavigation({ [currentQuestionNamespace, currentQuestionIndex, relevantQuestions] ) - const gotoPrevQuestion = (): string | undefined => { + const gotoPrevQuestion = () => { if (noPrevQuestion) { return undefined } @@ -62,7 +63,7 @@ export default function useNavigation({ return newCurrentQuestion } - const gotoNextQuestion = (): string | undefined => { + const gotoNextQuestion = () => { if (noNextQuestion) { return undefined } diff --git a/src/publicodes-state/hooks/useRule/index.ts b/src/publicodes-state/hooks/useRule/index.ts index d15f331a2..1494fb742 100644 --- a/src/publicodes-state/hooks/useRule/index.ts +++ b/src/publicodes-state/hooks/useRule/index.ts @@ -1,10 +1,11 @@ 'use client' import { carboneMetric } from '@/constants/metric' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { utils } from 'publicodes' import { useContext, useMemo } from 'react' import { SimulationContext } from '../../providers/simulationProvider/context' -import { DottedName, Metric, NGCEvaluatedNode, NGCRuleNode } from '../../types' +import { Metric, NGCEvaluatedNode } from '../../types' import useCurrentSimulation from '../useCurrentSimulation' import useChoices from './useChoices' import useContent from './useContent' @@ -67,7 +68,10 @@ export default function useRule( dottedName, }) - const parent = useMemo(() => utils.ruleParent(dottedName), [dottedName]) + const parent = useMemo( + () => utils.ruleParent(dottedName), + [dottedName] + ) as DottedName const { category, diff --git a/src/publicodes-state/hooks/useRule/useChoices.ts b/src/publicodes-state/hooks/useRule/useChoices.ts index 79344cd62..4609f8620 100644 --- a/src/publicodes-state/hooks/useRule/useChoices.ts +++ b/src/publicodes-state/hooks/useRule/useChoices.ts @@ -1,7 +1,7 @@ 'use client' +import { NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' -import { NGCRuleNode } from '../../types' type Props = { rule: NGCRuleNode | null | any // Model shenanigans: question alimentation . local . consommation is missing "formule" diff --git a/src/publicodes-state/hooks/useRule/useContent.ts b/src/publicodes-state/hooks/useRule/useContent.ts index 49efdab33..2016514b1 100644 --- a/src/publicodes-state/hooks/useRule/useContent.ts +++ b/src/publicodes-state/hooks/useRule/useContent.ts @@ -1,8 +1,13 @@ 'use client' import getNamespace from '@/publicodes-state/helpers/getNamespace' +import { + DottedName, + NGCRuleNode, + Suggestions, +} from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' -import { DottedName, NGCRuleNode, Suggestion } from '../../types' +import { FormattedSuggestion } from '../../types' type Props = { dottedName: DottedName @@ -10,13 +15,13 @@ type Props = { } export default function useContent({ dottedName, rule }: Props) { - const category = useMemo<string>(() => { + const category = useMemo(() => { const namespace = getNamespace(dottedName) ?? '' // This is only used by "ui . pédagogie" rules. For them, we need to extract the category from the dottedName (ui . pedagogie . [category]) if (namespace === 'ui') { - return dottedName.split(' . ')[3] + return dottedName.split(' . ')[3] as DottedName } - return namespace + return namespace as DottedName }, [dottedName]) const title = useMemo<string | undefined>(() => rule?.title, [rule]) @@ -40,8 +45,8 @@ export default function useContent({ dottedName, rule }: Props) { ) const unit = useMemo<string | undefined>(() => rule?.rawNode['unité'], [rule]) - const assistance = useMemo<string | undefined>( - () => rule?.rawNode['aide'], + const assistance = useMemo<DottedName | undefined>( + () => rule?.rawNode['aide'] as DottedName, [rule] ) @@ -57,15 +62,20 @@ export default function useContent({ dottedName, rule }: Props) { [rule] ) - const suggestions = useMemo<Suggestion[] | undefined>(() => { - const suggestionsFolder = - rule?.rawNode.mosaique?.suggestions || rule?.rawNode.suggestions - return suggestionsFolder - ? Object.keys(suggestionsFolder).map((key: string) => ({ - label: key, - value: suggestionsFolder[key], - })) + const suggestions = useMemo(() => { + const suggestionsFolder = (rule?.rawNode.mosaique?.suggestions || + rule?.rawNode.suggestions) as Suggestions + const suggestions = suggestionsFolder + ? Object.keys(suggestionsFolder).map( + (key) => + ({ + label: key, + value: suggestionsFolder[key as keyof typeof suggestionsFolder], + }) as FormattedSuggestion + ) : [] + + return suggestions }, [rule]) const excerpt = useMemo<string | undefined>( @@ -74,7 +84,7 @@ export default function useContent({ dottedName, rule }: Props) { ) // This is only used by "ui . pédagogie" rules - const actions = useMemo<string[] | undefined>( + const actions = useMemo<DottedName[] | undefined>( () => (rule as any)?.rawNode['actions'], [rule] ) diff --git a/src/publicodes-state/hooks/useRule/useMissing.ts b/src/publicodes-state/hooks/useRule/useMissing.ts index e5c1d31d6..a84d9c734 100644 --- a/src/publicodes-state/hooks/useRule/useMissing.ts +++ b/src/publicodes-state/hooks/useRule/useMissing.ts @@ -1,12 +1,13 @@ 'use client' +import { Situation } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' import getIsMissing from '../../helpers/getIsMissing' -import { DottedName, Situation } from '../../types' type Props = { dottedName: DottedName - questionsOfMosaicFromParent?: string[] + questionsOfMosaicFromParent?: DottedName[] situation: Situation foldedSteps: string[] } diff --git a/src/publicodes-state/hooks/useRule/useNotifications.ts b/src/publicodes-state/hooks/useRule/useNotifications.ts index 3f2b3d941..cc162afc7 100644 --- a/src/publicodes-state/hooks/useRule/useNotifications.ts +++ b/src/publicodes-state/hooks/useRule/useNotifications.ts @@ -1,13 +1,14 @@ 'use client' import getNamespace from '@/publicodes-state/helpers/getNamespace' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { PublicodesExpression } from 'publicodes' import { useMemo } from 'react' -import { DottedName, NGCEvaluatedNode, Situation } from '../../types' +import { NGCEvaluatedNode, Situation } from '../../types' type Props = { dottedName: DottedName - everyNotifications: string[] + everyNotifications: DottedName[] safeEvaluate: (rule: PublicodesExpression) => NGCEvaluatedNode | null situation: Situation } @@ -18,7 +19,7 @@ export default function useNotifications({ safeEvaluate, situation, }: Props) { - const notifications = useMemo<string[]>( + const notifications = useMemo( () => everyNotifications.filter( (notification) => { @@ -35,7 +36,7 @@ export default function useNotifications({ [dottedName, everyNotifications] ) - const activeNotifications = useMemo<string[]>( + const activeNotifications = useMemo( () => notifications.filter( (notification) => safeEvaluate(notification)?.nodeValue diff --git a/src/publicodes-state/hooks/useRule/useQuestionsOfMosaic.ts b/src/publicodes-state/hooks/useRule/useQuestionsOfMosaic.ts index 7759ca868..ad7cb7ac6 100644 --- a/src/publicodes-state/hooks/useRule/useQuestionsOfMosaic.ts +++ b/src/publicodes-state/hooks/useRule/useQuestionsOfMosaic.ts @@ -1,7 +1,7 @@ -import { DottedName } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' type Props = { - everyMosaicChildrenWithParent: Record<string, string[]> + everyMosaicChildrenWithParent: Record<DottedName, DottedName[]> dottedName: DottedName } export default function useQuestionsOfMosaic({ diff --git a/src/publicodes-state/hooks/useRule/useSetValue.ts b/src/publicodes-state/hooks/useRule/useSetValue.ts index df500f795..30052f797 100644 --- a/src/publicodes-state/hooks/useRule/useSetValue.ts +++ b/src/publicodes-state/hooks/useRule/useSetValue.ts @@ -1,22 +1,24 @@ 'use client' import getIsMissing from '@/publicodes-state/helpers/getIsMissing' +import { + DottedName, + NGCRuleNode, + NodeValue, +} from '@incubateur-ademe/nosgestesclimat' import { PublicodesExpression, utils } from 'publicodes' import { useCallback } from 'react' import getType from '../../helpers/getType' import { - DottedName, NGCEvaluatedNode, - NGCRuleNode, - NGCRulesNodes, - NodeValue, + ParsedRules, Situation, UpdateCurrentSimulationProps, } from '../../types' type Props = { dottedName: DottedName - parsedRules: NGCRulesNodes + parsedRules: ParsedRules safeGetRule: (rule: DottedName) => NGCRuleNode | null safeEvaluate: (rule: PublicodesExpression) => NGCEvaluatedNode | null evaluation: NGCEvaluatedNode | null @@ -81,7 +83,7 @@ export default function useSetValue({ */ const setValue = useCallback( async ( - value: NodeValue | { [dottedName: DottedName]: NodeValue }, + value: NodeValue | Record<string, NodeValue>, { foldedStep, questionsOfMosaicFromSibling, @@ -93,9 +95,7 @@ export default function useSetValue({ let situationToAdd = {} if (typeof value === 'object') { - situationToAdd = Object.keys( - value as { [dottedName: DottedName]: NodeValue } - ).reduce( + situationToAdd = Object.keys(value || {}).reduce( (accumulator: Situation, partialMosaicChildDottedName: string) => { const mosaicChildDottedName = utils.disambiguateReference( parsedRules, diff --git a/src/publicodes-state/hooks/useRule/useType.ts b/src/publicodes-state/hooks/useRule/useType.ts index 9788b08c8..fb9ac0733 100644 --- a/src/publicodes-state/hooks/useRule/useType.ts +++ b/src/publicodes-state/hooks/useRule/useType.ts @@ -1,8 +1,9 @@ 'use client' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' import getType from '../../helpers/getType' -import { DottedName, NGCEvaluatedNode, NGCRuleNode } from '../../types' +import { NGCEvaluatedNode } from '../../types' type Props = { dottedName: DottedName diff --git a/src/publicodes-state/hooks/useRule/useValue.ts b/src/publicodes-state/hooks/useRule/useValue.ts index f98abebe0..1f021f27f 100644 --- a/src/publicodes-state/hooks/useRule/useValue.ts +++ b/src/publicodes-state/hooks/useRule/useValue.ts @@ -1,7 +1,8 @@ 'use client' +import { NodeValue } from '@incubateur-ademe/nosgestesclimat' import { useMemo } from 'react' -import { NGCEvaluatedNode, NodeValue } from '../../types' +import { NGCEvaluatedNode } from '../../types' type Props = { evaluation: NGCEvaluatedNode | null diff --git a/src/publicodes-state/hooks/useTempEngine/index.ts b/src/publicodes-state/hooks/useTempEngine/index.ts index 6a625086b..9008c4962 100644 --- a/src/publicodes-state/hooks/useTempEngine/index.ts +++ b/src/publicodes-state/hooks/useTempEngine/index.ts @@ -1,4 +1,5 @@ -import { DottedName } from '@/publicodes-state/types' +import { NGCEvaluatedNode } from '@/publicodes-state/types' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { useContext } from 'react' import { SimulationContext } from '../../providers/simulationProvider/context' import useCurrentSimulation from '../useCurrentSimulation' @@ -12,8 +13,15 @@ export default function useTempEngine() { const { foldedSteps } = useCurrentSimulation() - const getRuleObject = (dottedName: DottedName): any => { - return { ...safeEvaluate(dottedName), ...safeGetRule(dottedName) } + const getSpecialRuleObject = ( + dottedName: DottedName + ): NGCEvaluatedNode & NGCRuleNode => { + const evaluation = safeEvaluate(dottedName) as NGCEvaluatedNode + const rule = safeGetRule(dottedName) as NGCRuleNode + return { + ...evaluation, + ...rule, + } } const extendedFoldedSteps = foldedSteps @@ -24,7 +32,7 @@ export default function useTempEngine() { .flat() return { - getRuleObject, + getSpecialRuleObject, rules, extendedFoldedSteps, } diff --git a/src/publicodes-state/hooks/useUser/useActions.ts b/src/publicodes-state/hooks/useUser/useActions.ts index 0b3b45539..a370983a2 100644 --- a/src/publicodes-state/hooks/useUser/useActions.ts +++ b/src/publicodes-state/hooks/useUser/useActions.ts @@ -1,8 +1,5 @@ -import { - DottedName, - Simulation, - UpdateCurrentSimulationProps, -} from '../../types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' +import { Simulation, UpdateCurrentSimulationProps } from '../../types' type Props = { currentSimulation: Simulation diff --git a/src/publicodes-state/providers/formProvider/Provider.tsx b/src/publicodes-state/providers/formProvider/Provider.tsx index 79a75bddd..e297e8fb4 100644 --- a/src/publicodes-state/providers/formProvider/Provider.tsx +++ b/src/publicodes-state/providers/formProvider/Provider.tsx @@ -1,6 +1,8 @@ 'use client' import { useCurrentSimulation } from '@/publicodes-state' + +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { PropsWithChildren, useContext } from 'react' import { SimulationContext } from '../simulationProvider/context' import FormContext from './context' @@ -9,7 +11,7 @@ import useProgression from './useProgression' import useQuestions from './useQuestions' type Props = { - root?: string + root?: DottedName } export default function FormProvider({ @@ -36,19 +38,23 @@ export default function FormProvider({ setCurrentCategory, } = useCurrent() - const { remainingQuestions, relevantAnsweredQuestions, relevantQuestions } = - useQuestions({ - root, - safeGetRule, - safeEvaluate, - categories, - subcategories, - foldedSteps, - situation, - everyQuestions, - everyMosaicChildrenWithParent, - rawMissingVariables, - }) + const { + remainingQuestions, + relevantAnsweredQuestions, + relevantQuestions, + questionsByCategories, + } = useQuestions({ + root, + safeGetRule, + safeEvaluate, + categories, + subcategories, + foldedSteps, + situation, + everyQuestions, + everyMosaicChildrenWithParent, + rawMissingVariables, + }) const { remainingQuestionsByCategories } = useProgression({ categories, @@ -60,6 +66,7 @@ export default function FormProvider({ return ( <FormContext.Provider value={{ + questionsByCategories, relevantQuestions, remainingQuestions, relevantAnsweredQuestions, diff --git a/src/publicodes-state/providers/formProvider/context.ts b/src/publicodes-state/providers/formProvider/context.ts index 7333ee549..b38156a71 100644 --- a/src/publicodes-state/providers/formProvider/context.ts +++ b/src/publicodes-state/providers/formProvider/context.ts @@ -1,22 +1,25 @@ 'use client' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { createContext } from 'react' type FormContextType = { - relevantQuestions: string[] - remainingQuestions: string[] - relevantAnsweredQuestions: string[] - remainingQuestionsByCategories: Record<string, string[]> - currentQuestion: string | null - currentCategory: string | null - setCurrentQuestion: (question: string | null) => void - setCurrentCategory: (category: string | null) => void + questionsByCategories: Record<DottedName, DottedName[]> + relevantQuestions: DottedName[] + remainingQuestions: DottedName[] + relevantAnsweredQuestions: DottedName[] + remainingQuestionsByCategories: Record<DottedName, DottedName[]> + currentQuestion: DottedName | null + currentCategory: DottedName | null + setCurrentQuestion: (question: DottedName | null) => void + setCurrentCategory: (category: DottedName | null) => void } export default createContext<FormContextType>({ + questionsByCategories: {} as Record<DottedName, DottedName[]>, relevantQuestions: [], remainingQuestions: [], relevantAnsweredQuestions: [], - remainingQuestionsByCategories: {}, + remainingQuestionsByCategories: {} as Record<DottedName, DottedName[]>, currentQuestion: null, currentCategory: null, setCurrentQuestion: () => '', diff --git a/src/publicodes-state/providers/formProvider/index.tsx b/src/publicodes-state/providers/formProvider/index.tsx index 8253e786a..62203ccf1 100644 --- a/src/publicodes-state/providers/formProvider/index.tsx +++ b/src/publicodes-state/providers/formProvider/index.tsx @@ -1,11 +1,12 @@ 'use client' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { PropsWithChildren, useContext, useMemo } from 'react' import { SimulationContext } from '../simulationProvider/context' import Provider from './Provider' type Props = { - root?: string + root?: DottedName } /** diff --git a/src/publicodes-state/providers/formProvider/useCurrent.ts b/src/publicodes-state/providers/formProvider/useCurrent.ts index ae2cb9ea6..263a5807c 100644 --- a/src/publicodes-state/providers/formProvider/useCurrent.ts +++ b/src/publicodes-state/providers/formProvider/useCurrent.ts @@ -1,4 +1,5 @@ import getNamespace from '@/publicodes-state/helpers/getNamespace' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useCallback, useState } from 'react' /** @@ -7,12 +8,16 @@ import { useCallback, useState } from 'react' * currentCategory could be inferred from current question. It is not deleted for now because it is used in the actions page */ export default function useCurrent() { - const [currentQuestion, setCurrentQuestion] = useState<null | string>(null) + const [currentQuestion, setCurrentQuestion] = useState<null | DottedName>( + null + ) - const [currentCategory, setCurrentCategory] = useState<null | string>(null) + const [currentCategory, setCurrentCategory] = useState<null | DottedName>( + null + ) const exportedSetCurrentQuestion = useCallback( - (newCurrentQuestion: string | null) => { + (newCurrentQuestion: DottedName | null) => { setCurrentQuestion(newCurrentQuestion) const newCurrentQuestionNamespace = getNamespace(newCurrentQuestion) if (newCurrentQuestion && newCurrentQuestionNamespace) { diff --git a/src/publicodes-state/providers/formProvider/useProgression.ts b/src/publicodes-state/providers/formProvider/useProgression.ts index ccebffb23..494d4c83a 100644 --- a/src/publicodes-state/providers/formProvider/useProgression.ts +++ b/src/publicodes-state/providers/formProvider/useProgression.ts @@ -1,10 +1,11 @@ import { UpdateCurrentSimulationProps } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useEffect, useMemo } from 'react' type Props = { - categories: string[] - remainingQuestions: string[] - relevantQuestions: string[] + categories: DottedName[] + remainingQuestions: DottedName[] + relevantQuestions: DottedName[] updateCurrentSimulation: (simulation: UpdateCurrentSimulationProps) => void } @@ -28,16 +29,16 @@ export default function useProgression({ [relevantQuestions, remainingQuestions] ) - const remainingQuestionsByCategories = useMemo<Record<string, string[]>>( + const remainingQuestionsByCategories = useMemo( () => categories.reduce( - (accumulator: Record<string, string[]>, currentValue: string) => ({ + (accumulator, currentValue) => ({ ...accumulator, [currentValue]: remainingQuestions.filter((question) => question.includes(currentValue) ), }), - {} + {} as Record<DottedName, DottedName[]> ), [remainingQuestions, categories] ) diff --git a/src/publicodes-state/providers/formProvider/useQuestions.ts b/src/publicodes-state/providers/formProvider/useQuestions.ts index 83ac24548..ebaa2b882 100644 --- a/src/publicodes-state/providers/formProvider/useQuestions.ts +++ b/src/publicodes-state/providers/formProvider/useQuestions.ts @@ -1,27 +1,27 @@ -import { DottedName as NGCDottedName } from '@incubateur-ademe/nosgestesclimat' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' import { PublicodesExpression } from 'publicodes' import { useMemo } from 'react' import getIsMissing from '../../helpers/getIsMissing' import getSortedQuestionsList from '@/publicodes-state/helpers/getSortedQuestionsList' import { - DottedName, + Entries, + MissingVariables, NGCEvaluatedNode, - NGCRuleNode, Situation, } from '../../types' type Props = { - root: string + root: DottedName safeGetRule: (rule: DottedName) => NGCRuleNode | null safeEvaluate: (rule: PublicodesExpression) => NGCEvaluatedNode | null - categories: string[] - subcategories: Record<string, string[]> + categories: DottedName[] + subcategories: Record<DottedName, DottedName[]> situation: Situation - foldedSteps: string[] - everyQuestions: string[] - everyMosaicChildrenWithParent: Record<string, string[]> - rawMissingVariables: Record<string, number> + foldedSteps: DottedName[] + everyQuestions: DottedName[] + everyMosaicChildrenWithParent: Record<DottedName, DottedName[]> + rawMissingVariables: MissingVariables } /** @@ -39,34 +39,41 @@ export default function useQuestions({ rawMissingVariables, }: Props) { // We use the DottedName type from nosgestesclimat to make sure the build will break when using rules that are not in the model. - const priorityQuestions: NGCDottedName[] = ['alimentation . plats'] + const priorityQuestions: DottedName[] = ['alimentation . plats'] - const nonPriorityQuestions: NGCDottedName[] = [ + const nonPriorityQuestions: DottedName[] = [ 'logement . électricité . réseau . consommation', ] - const missingVariables = useMemo<Record<string, number>>( + const missingVariables = useMemo( () => { const tempMissingVariables = Object.fromEntries( - Object.entries(safeEvaluate(root)?.missingVariables || {}).filter( - (missingVariable) => everyQuestions.includes(missingVariable[0]) + ( + Object.entries( + safeEvaluate(root)?.missingVariables || {} + ) as Entries<MissingVariables> + ).filter((missingVariable) => + everyQuestions.includes(missingVariable[0]) ) - ) + ) as MissingVariables // We take every mosaic parent to add it to the missing variables with the max score of its children - Object.entries(everyMosaicChildrenWithParent).forEach( - ([mosaicParent, mosaicChildren]) => { - const maxMissingVariableScoreInMosaic = Math.max( - ...mosaicChildren.map((child) => tempMissingVariables[child]) - ) - if (!isNaN(maxMissingVariableScoreInMosaic)) { - tempMissingVariables[mosaicParent] = maxMissingVariableScoreInMosaic - mosaicChildren.forEach((mosaicChild) => { - delete tempMissingVariables[mosaicChild] - }) - } + ;( + Object.entries(everyMosaicChildrenWithParent) as [ + DottedName, + DottedName[], + ][] + ).forEach(([mosaicParent, mosaicChildren]) => { + const maxMissingVariableScoreInMosaic = Math.max( + ...mosaicChildren.map((child) => tempMissingVariables[child]) + ) + if (!isNaN(maxMissingVariableScoreInMosaic)) { + tempMissingVariables[mosaicParent] = maxMissingVariableScoreInMosaic + mosaicChildren.forEach((mosaicChild) => { + delete tempMissingVariables[mosaicChild] + }) } - ) + }) // We artificially set the missing variables of the whiteList to a high value priorityQuestions.forEach((dottedName) => { @@ -91,7 +98,7 @@ export default function useQuestions({ ] ) - const remainingQuestions = useMemo<string[]>(() => { + const remainingQuestions = useMemo(() => { // We take every questions const questionsToSort = everyQuestions // We remove all that are in mosaics, @@ -126,7 +133,7 @@ export default function useQuestions({ subcategories, ]) - const relevantAnsweredQuestions = useMemo<string[]>( + const relevantAnsweredQuestions = useMemo( () => foldedSteps.filter((foldedStep) => { // checks that there is still a question associated to the folded step @@ -148,7 +155,7 @@ export default function useQuestions({ [situation, foldedSteps, safeEvaluate, everyQuestions, rawMissingVariables] ) - const tempRelevantQuestions = useMemo<string[]>( + const tempRelevantQuestions = useMemo( () => [ /** * We add every answered questions to display and every not answered @@ -181,7 +188,7 @@ export default function useQuestions({ * * (yes, this is shit) */ - const relevantQuestions = useMemo<string[]>(() => { + const relevantQuestions = useMemo(() => { const questions = tempRelevantQuestions.filter( (question, index) => tempRelevantQuestions.indexOf(question) === index ) @@ -193,10 +200,25 @@ export default function useQuestions({ }) }, [categories, missingVariables, subcategories, tempRelevantQuestions]) + const questionsByCategories = useMemo( + () => + categories.reduce( + (accumulator, currentValue) => ({ + ...accumulator, + [currentValue]: relevantQuestions.filter((question) => + question.includes(currentValue) + ), + }), + {} as Record<DottedName, DottedName[]> + ), + [relevantQuestions, categories] + ) + return { missingVariables, remainingQuestions, relevantAnsweredQuestions, relevantQuestions, + questionsByCategories, } } diff --git a/src/publicodes-state/providers/simulationProvider/context.ts b/src/publicodes-state/providers/simulationProvider/context.ts index 9ffdee0f4..ead64070f 100644 --- a/src/publicodes-state/providers/simulationProvider/context.ts +++ b/src/publicodes-state/providers/simulationProvider/context.ts @@ -1,13 +1,17 @@ 'use client' -import { NGCRules } from '@incubateur-ademe/nosgestesclimat' -import Engine, { PublicodesExpression } from 'publicodes' -import { createContext } from 'react' import { DottedName, + NGCRuleNode, + NGCRules, +} from '@incubateur-ademe/nosgestesclimat' +import { PublicodesExpression } from 'publicodes' +import { createContext } from 'react' +import { + Engine, Metric, + MissingVariables, NGCEvaluatedNode, - NGCRuleNode, - NGCRulesNodes, + ParsedRules, Situation, } from '../../types' @@ -20,14 +24,14 @@ type SimulationContextType = { rule: PublicodesExpression, metric?: Metric ) => NGCEvaluatedNode | null - parsedRules: NGCRulesNodes + parsedRules: ParsedRules everyRules: DottedName[] everyInactiveRules: DottedName[] everyQuestions: DottedName[] everyNotifications: DottedName[] everyUiCategories: DottedName[] everyMosaicChildrenWithParent: Record<DottedName, DottedName[]> - rawMissingVariables: Record<string, number> + rawMissingVariables: MissingVariables categories: DottedName[] subcategories: Record<DottedName, DottedName[]> addToEngineSituation: (situationToAdd: Situation) => Situation @@ -39,16 +43,16 @@ export const SimulationContext = createContext<SimulationContextType>({ pristineEngine: null, safeGetRule: () => null, safeEvaluate: () => null, - parsedRules: {}, + parsedRules: {} as ParsedRules, everyRules: [], everyInactiveRules: [], everyQuestions: [], everyNotifications: [], everyUiCategories: [], - everyMosaicChildrenWithParent: {}, - rawMissingVariables: {}, + everyMosaicChildrenWithParent: {} as Record<DottedName, DottedName[]>, + rawMissingVariables: {} as MissingVariables, categories: [], - subcategories: {}, + subcategories: {} as Record<DottedName, DottedName[]>, addToEngineSituation: () => ({}) as Situation, isInitialized: false, }) diff --git a/src/publicodes-state/providers/simulationProvider/index.tsx b/src/publicodes-state/providers/simulationProvider/index.tsx index afcf823fb..1c004b2d9 100644 --- a/src/publicodes-state/providers/simulationProvider/index.tsx +++ b/src/publicodes-state/providers/simulationProvider/index.tsx @@ -2,8 +2,8 @@ import { PropsWithChildren } from 'react' -import { NGCRules } from '@incubateur-ademe/nosgestesclimat' -import { DottedName } from '../../types' +import Loader from '@/design-system/layout/Loader' +import { DottedName, NGCRules } from '@incubateur-ademe/nosgestesclimat' import { SimulationContext } from './context' import { useCategories } from './useCategories' import { useEngine } from './useEngine' @@ -52,6 +52,14 @@ export default function SimulationProvider({ safeEvaluate, }) + if (!isInitialized) { + return ( + <div className="flex h-screen flex-1 items-center justify-center"> + <Loader color="dark" /> + </div> + ) + } + return ( <SimulationContext.Provider value={{ diff --git a/src/publicodes-state/providers/simulationProvider/useCategories.ts b/src/publicodes-state/providers/simulationProvider/useCategories.ts index c55ded7c6..efc543562 100644 --- a/src/publicodes-state/providers/simulationProvider/useCategories.ts +++ b/src/publicodes-state/providers/simulationProvider/useCategories.ts @@ -1,14 +1,18 @@ import { orderedCategories } from '@/constants/orderedCategories' import getSomme from '@/publicodes-state/helpers/getSomme' +import { + DottedName, + NGCRuleNode, + NGCRulesNodes, +} from '@incubateur-ademe/nosgestesclimat' import * as Sentry from '@sentry/react' import { utils } from 'publicodes' import { useMemo } from 'react' -import { DottedName, NGCRuleNode, NGCRulesNodes } from '../../types' type Props = { parsedRules: NGCRulesNodes everyRules: DottedName[] - root: string + root: DottedName safeGetRule: (rule: DottedName) => NGCRuleNode | null } @@ -45,46 +49,49 @@ export function useCategories({ ) }, [root, safeGetRule]) - const subcategories = useMemo<Record<string, string[]>>(() => { - return categories.reduce((accumulator: object, currentValue: string) => { - const subCat = [] - const rule = safeGetRule(currentValue) - if (!rule) { - console.error( - `[useCategories:subcategories] No rule found for ${currentValue}` - ) - Sentry.captureMessage( - `[useCategories:subcategories] No rule found for ${currentValue}` - ) - return accumulator - } - - const sum = getSomme(rule.rawNode) - if (!sum) { - console.error( - `[useCategories:subcategories] No [somme] found for ${currentValue}` - ) - Sentry.captureMessage( - `[useCategories:subcategories] No [somme] found for ${currentValue}` - ) - return accumulator - } + const subcategories = useMemo<Record<DottedName, DottedName[]>>(() => { + return categories.reduce( + (accumulator, currentValue: DottedName) => { + const subCat = [] + const rule = safeGetRule(currentValue) + if (!rule) { + console.error( + `[useCategories:subcategories] No rule found for ${currentValue}` + ) + Sentry.captureMessage( + `[useCategories:subcategories] No rule found for ${currentValue}` + ) + return accumulator + } - for (const rule of sum) { - // The rule is a full rule, not a shorten one - if (everyRules.includes(rule)) { - subCat.push(rule) - } else { - subCat.push( - utils.disambiguateReference(parsedRules, currentValue, rule) + const sum = getSomme(rule.rawNode) + if (!sum) { + console.error( + `[useCategories:subcategories] No [somme] found for ${currentValue}` + ) + Sentry.captureMessage( + `[useCategories:subcategories] No [somme] found for ${currentValue}` ) + return accumulator } - } - return { - ...accumulator, - [currentValue]: subCat, - } - }, {}) + + for (const rule of sum) { + // The rule is a full rule, not a shorten one + if (everyRules.includes(rule)) { + subCat.push(rule) + } else { + subCat.push( + utils.disambiguateReference(parsedRules, currentValue, rule) + ) + } + } + return { + ...accumulator, + [currentValue]: subCat, + } + }, + {} as Record<DottedName, DottedName[]> + ) }, [parsedRules, categories, safeGetRule, everyRules]) return { categories, subcategories } diff --git a/src/publicodes-state/providers/simulationProvider/useEngine.ts b/src/publicodes-state/providers/simulationProvider/useEngine.ts index 60dd61346..be2ff800d 100644 --- a/src/publicodes-state/providers/simulationProvider/useEngine.ts +++ b/src/publicodes-state/providers/simulationProvider/useEngine.ts @@ -1,10 +1,15 @@ import { carboneMetric } from '@/constants/metric' import { safeGetRuleHelper } from '@/publicodes-state/helpers/safeGetRuleHelper' +import { + DottedName, + NGCRuleNode, + NGCRules, +} from '@incubateur-ademe/nosgestesclimat' import { captureException } from '@sentry/react' import Engine, { PublicodesExpression } from 'publicodes' import { useCallback, useMemo } from 'react' import { safeEvaluateHelper } from '../../helpers/safeEvaluateHelper' -import { DottedName, Metric, NGCRuleNode, Rules } from '../../types' +import { Metric } from '../../types' /** * Initiate the engine based on the rules we pass @@ -13,13 +18,13 @@ import { DottedName, Metric, NGCRuleNode, Rules } from '../../types' * * And a pristine engine wich can be used to assess rules without any situation (for exemple, we can reliably sort the subcategories this way) */ -export function useEngine(rules: Rules) { +export function useEngine(rules: NGCRules) { if (!rules) throw new Error('Missing rules') - const engine = useMemo<Engine>(() => { + const engine = useMemo(() => { const nbRules = Object.keys(rules).length console.time(`⚙️ Parsing ${nbRules}`) - const engine = new Engine(rules, { + const engine = new Engine<DottedName>(rules, { logger: { log(msg: string) { console.log(`[publicodes:log] ${msg}`) diff --git a/src/publicodes-state/providers/simulationProvider/useEngineSituation.ts b/src/publicodes-state/providers/simulationProvider/useEngineSituation.ts index f729c50e2..b28ff17be 100644 --- a/src/publicodes-state/providers/simulationProvider/useEngineSituation.ts +++ b/src/publicodes-state/providers/simulationProvider/useEngineSituation.ts @@ -1,7 +1,6 @@ import { useCurrentSimulation } from '@/publicodes-state' -import Engine from 'publicodes' import { useCallback, useEffect, useState } from 'react' -import { Situation } from '../../types' +import { Engine, Situation } from '../../types' type Props = { engine: Engine diff --git a/src/publicodes-state/providers/simulationProvider/useRules.ts b/src/publicodes-state/providers/simulationProvider/useRules.ts index d17389dc1..1f87087e3 100644 --- a/src/publicodes-state/providers/simulationProvider/useRules.ts +++ b/src/publicodes-state/providers/simulationProvider/useRules.ts @@ -1,104 +1,113 @@ -import { NGCRuleNode, NGCRulesNodes } from '@/publicodes-state/types' -import Engine, { utils } from 'publicodes' +import { Engine, Entries, ParsedRules } from '@/publicodes-state/types' +import { DottedName, NGCRuleNode } from '@incubateur-ademe/nosgestesclimat' +import { utils } from 'publicodes' import { useMemo } from 'react' +import { MissingVariables } from './../../types.d' type Props = { engine: Engine - root: string + root: DottedName } export function useRules({ engine, root }: Props) { - const parsedRules = useMemo<NGCRulesNodes>( - () => engine.getParsedRules(), - [engine] - ) - const parsedRulesEntries = useMemo<[string, NGCRuleNode][]>( - () => Object.entries(parsedRules), + const parsedRules = useMemo(() => engine.getParsedRules(), [engine]) + + const parsedRulesEntries = useMemo( + () => Object.entries(parsedRules) as Entries<ParsedRules>, [parsedRules] ) - const everyRules = useMemo<string[]>( - () => parsedRulesEntries.map((rule: (string | any)[]) => rule[0]), + const everyRules = useMemo( + () => parsedRulesEntries.map((rule) => rule[0]), [parsedRulesEntries] ) - const everyInactiveRules = useMemo<string[]>( + const everyInactiveRules = useMemo( () => parsedRulesEntries - .filter((rule: (string | any)[]) => rule[1].rawNode.inactif === 'oui') - .map((rule: (string | any)[]) => rule[0]), + .filter((rule) => rule[1].rawNode.inactif === 'oui') + .map((rule) => rule[0]), [parsedRulesEntries] ) - const everyQuestions = useMemo<string[]>( + const everyQuestions = useMemo( () => parsedRulesEntries - .filter((rule: (string | any)[]) => rule[1].rawNode.question) - .map((question: (string | any)[]) => question[0]), + .filter((rule) => rule[1].rawNode.question) + .map((question) => question[0]), [parsedRulesEntries] ) - const everyNotifications = useMemo<string[]>( + const everyNotifications = useMemo( () => parsedRulesEntries - .filter((rule: any) => rule[1].rawNode.type === 'notification') // Model shenanigans: type is only used for notifications + .filter((rule) => rule[1].rawNode.type === 'notification') .map((question) => question[0]), [parsedRulesEntries] ) - const everyUiCategories = useMemo<string[]>( + const everyUiCategories = useMemo( () => parsedRulesEntries - .filter((rule: any) => rule[0].includes('ui . pédagogie')) - .filter((rule: any) => !rule[1].rawNode['cachée']) + .filter((rule) => rule[0].includes('ui . pédagogie')) + .filter((rule) => !rule[1].rawNode['cachée']) .map((question) => question[0]), [parsedRulesEntries] ) - const everyMosaic = useMemo<string[]>( + const everyMosaic = useMemo<DottedName[]>( () => parsedRulesEntries - .filter((rule: (string | any)[]) => rule[1].rawNode.mosaique) + .filter((rule) => rule[1].rawNode.mosaique) .map((question) => question[0]), [parsedRulesEntries] ) - const everyMosaicChildrenWithParent = useMemo<Record<string, string[]>>( + const everyMosaicChildrenWithParent = useMemo( () => - everyMosaic.reduce<Record<string, string[]>>((accumulator, mosaic) => { - const mosaicRule = engine.getRule(mosaic) as NGCRuleNode + everyMosaic.reduce<Record<DottedName, DottedName[]>>( + (accumulator, mosaic) => { + const mosaicRule = engine.getRule(mosaic) as NGCRuleNode - if (!mosaicRule.rawNode.mosaique) { - return accumulator - } - const mosaicChildren = mosaicRule.rawNode.mosaique['options']?.map( - (option: string) => { - // Stylax but shoudn't we use `utils.disambiguateReference` here ? - return utils.disambiguateReference(parsedRules, mosaic, option) + if (!mosaicRule.rawNode.mosaique) { + return accumulator } - ) - accumulator[mosaic] = [...mosaicChildren] - return accumulator - }, {}), + const mosaicChildren = mosaicRule.rawNode.mosaique['options']?.map( + (option: string) => { + return utils.disambiguateReference(parsedRules, mosaic, option) + } + ) + accumulator[mosaic] = [...mosaicChildren] + return accumulator + }, + {} as Record<DottedName, DottedName[]> + ), [everyMosaic, engine, parsedRules] ) - const rawMissingVariables = useMemo<Record<string, number>>(() => { - return Object.fromEntries( - Object.entries(engine.evaluate(root)?.missingVariables || {}).filter( - (missingVariable) => { - return ( - everyQuestions.includes(missingVariable[0]) && - parsedRules[missingVariable[0]].explanation.valeur.rawNode?.[ - 'applicable si' - ] === undefined && - parsedRules[missingVariable[0]].explanation.valeur.rawNode?.[ - 'non applicable si' - ] === undefined - ) - } - ) + const rawMissingVariables = useMemo(() => { + const MissingVariablesEntries = Object.entries( + engine.evaluate(root)?.missingVariables || {} + ) as Entries<MissingVariables> + + // We only keep missing variables that are questions and have no condition + const filteredMissingVariablesEntries = MissingVariablesEntries.filter( + ([missingVariableKey]) => { + return ( + everyQuestions.includes(missingVariableKey) && + parsedRules[missingVariableKey].explanation.valeur.rawNode?.[ + 'applicable si' + ] === undefined && + parsedRules[missingVariableKey].explanation.valeur.rawNode?.[ + 'non applicable si' + ] === undefined + ) + } ) + + return Object.fromEntries( + filteredMissingVariablesEntries + ) as MissingVariables }, [engine, everyQuestions, parsedRules, root]) return { diff --git a/src/publicodes-state/providers/simulationProvider/useSetComputedResults.ts b/src/publicodes-state/providers/simulationProvider/useSetComputedResults.ts index 1c36283a8..05a19f35e 100644 --- a/src/publicodes-state/providers/simulationProvider/useSetComputedResults.ts +++ b/src/publicodes-state/providers/simulationProvider/useSetComputedResults.ts @@ -1,10 +1,10 @@ import { defaultMetric, metrics } from '@/constants/metric' import { useCurrentSimulation } from '@/publicodes-state' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' import { useCallback, useEffect, useMemo, useRef } from 'react' import { ComputedResults, ComputedResultsFootprint, - DottedName, Metric, NGCEvaluatedNode, } from '../../types' diff --git a/src/publicodes-state/types.d.ts b/src/publicodes-state/types.d.ts index b828dad02..e810213d4 100644 --- a/src/publicodes-state/types.d.ts +++ b/src/publicodes-state/types.d.ts @@ -1,13 +1,21 @@ import { Group } from '@/types/groups' -import { NGCRule } from '@incubateur-ademe/nosgestesclimat' import { + DottedName, + Metrics, + SuggestionValue, +} from '@incubateur-ademe/nosgestesclimat' +import PublicodesEngine, { EvaluatedNode, - Evaluation, + ParsedRules as PublicodesParsedRules, Situation as PublicodesSituation, - RuleNode, } from 'publicodes' -export type DottedName = string +// Utils + +// Could be in index.d.ts as ambiant type +export type Entries<T> = [keyof T, T[keyof T]][] + +// User and simulation types export type UserOrganisationInfo = { administratorEmail?: string @@ -32,60 +40,19 @@ export type User = { administratorEmail?: string } -export type Rules = any - export type Tutorials = Record<string, boolean> -export type Situation = PublicodesSituation<DottedName> - -export type Suggestion = { - label: string - value: any // TODO: sorry... - // value: - // | NodeValue - // | { - // [key: string]: NodeValue - // } -} - -export type NGCRuleNode = RuleNode & { - rawNode: NGCRule -} - -export type NGCRulesNodes = Record<DottedName, NGCRuleNode> - -export type NGCEvaluatedNode = EvaluatedNode -export type ActionChoices = Record<string, boolean> - -export type NodeValue = Evaluation - export type ComputedResultsFootprint = { bilan: number categories: Record<DottedName, number> } export type ComputedResults = Record<Metric, ComputedResultsFootprint> -export type Simulation = { - id: string - date: Date | string - situation: Situation - foldedSteps: DottedName[] - actionChoices: ActionChoices - persona?: DottedName - computedResults: ComputedResults - progression: number - defaultAdditionalQuestionsAnswers?: Record<string, string> - customAdditionalQuestionsAnswers?: Record<string, string> - polls?: string[] | null - groups?: string[] | null - savedViaEmail?: boolean -} - -type UpdateCurrentSimulationProps = { +export type UpdateCurrentSimulationProps = { situation?: Situation situationToAdd?: Situation - foldedStepToAdd?: string - actionChoices?: ActionChoices + foldedStepToAdd?: DottedName + actionChoices?: any defaultAdditionalQuestionsAnswers?: Record<string, string> customAdditionalQuestionsAnswers?: Record<string, string> computedResults?: ComputedResults @@ -97,12 +64,20 @@ type UpdateCurrentSimulationProps = { savedViaEmail?: boolean } -export type Persona = { - nom: string - icônes: string +export type Simulation = { + id: string + date: Date | string situation: Situation - description?: string - résumé: string + foldedSteps: DottedName[] + actionChoices: any + persona?: string + computedResults: ComputedResults + progression: number + defaultAdditionalQuestionsAnswers?: Record<string, string> + customAdditionalQuestionsAnswers?: Record<string, string> + polls?: string[] | null + groups?: string[] | null + savedViaEmail?: boolean } export type LocalStorage = { @@ -113,26 +88,22 @@ export type LocalStorage = { groupToRedirectToAfterTest?: Group } -type Color = `#${string}` +// Not used for now +export type ActionChoices = Record<DottedName, boolean> -type SuggestionsNode = Record< - string, - string | number | Record<string, string | number> -> +export type Metric = Metrics -type MosaiqueNode = { - type: 'selection' | 'nombre' - options: DottedName[] - total?: number - suggestions?: SuggestionsNode -} +export type Situation = PublicodesSituation<DottedName> -type MosaicInfos = { - mosaicRule: RuleNode - mosaicParams: MosaiqueNode - mosaicDottedNames: [DottedName, NGCRuleNode][] -} +export type ParsedRules = PublicodesParsedRules<DottedName> -type Formule = any +export type Engine = PublicodesEngine<DottedName> -export type Metric = 'carbone' | 'eau' +export type MissingVariables = Record<DottedName, number> + +export type NGCEvaluatedNode = EvaluatedNode + +export type FormattedSuggestion = { + label: string + value: SuggestionValue | Record<string, SuggestionValue> +} diff --git a/src/types/groups.ts b/src/types/groups.ts index 6d8b32d4e..d399263fb 100644 --- a/src/types/groups.ts +++ b/src/types/groups.ts @@ -1,4 +1,5 @@ import { Simulation } from '@/publicodes-state/types' +import { DottedName } from '@incubateur-ademe/nosgestesclimat' export type Participant = { _id: string @@ -22,21 +23,25 @@ export type Group = { } export type ValueObject = { - name: string + name: DottedName value: number mean?: number difference?: number isCategory?: boolean } -export type Points = { +export type CategoriesAndSubcategoriesFootprintsType = { + [key in DottedName]: ValueObject +} + +export type PointsFortsFaiblesType = { key: string resultObject: ValueObject } export type Results = { - userFootprintByCategoriesAndSubcategories: Record<string, ValueObject> - groupFootprintByCategoriesAndSubcategories: Record<string, ValueObject> - pointsForts: Points[] - pointsFaibles: Points[] + currentUserCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + groupCategoriesAndSubcategoriesFootprints: CategoriesAndSubcategoriesFootprintsType + pointsForts: PointsFortsFaiblesType[] + pointsFaibles: PointsFortsFaiblesType[] } diff --git a/src/types/organisations.ts b/src/types/organisations.ts index ecda3de1a..c177aaf06 100644 --- a/src/types/organisations.ts +++ b/src/types/organisations.ts @@ -32,7 +32,7 @@ export type CustomAdditionalQuestions = { export type OrganisationPoll = { _id: string - simulations: [OrganisationSimulation] + simulations: [Simulation] startDate: Date endDate: Date name: string diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts new file mode 100644 index 000000000..5602b755d --- /dev/null +++ b/src/utils/debounce.ts @@ -0,0 +1,11 @@ +export function debounce<T extends (...args: any[]) => void>( + func: T, + delay: number +): (...args: Parameters<T>) => void { + let timeoutId: NodeJS.Timeout + + return (...args: Parameters<T>) => { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => func(...args), delay) + } +} diff --git a/src/utils/formatDataForDB.ts b/src/utils/formatDataForDB.ts index d0756de90..ba6f79b5f 100644 --- a/src/utils/formatDataForDB.ts +++ b/src/utils/formatDataForDB.ts @@ -1,11 +1,15 @@ import { Situation } from '@/publicodes-state/types' +import { Situation as PublicodesSituation } from 'publicodes' -export function unformatSituation(situation?: Situation) { +// It shoudnlt be a situation type as it is a formatted situation from DB. +export function unformatSituation( + situation?: PublicodesSituation<string> +): Situation { return Object.entries({ ...situation }).reduce( - (acc: Situation, [key, value]: [string, any]) => { + (acc: Situation, [key, value]) => { // Key is not formatted if (!key.includes('_')) { - acc[key] = value + acc[key as keyof Situation] = value return acc } @@ -31,10 +35,10 @@ export function unformatSituation(situation?: Situation) { } } - acc[unformattedKey] = value + acc[unformattedKey as keyof Situation] = value return acc }, - {} + {} as Situation ) } diff --git a/src/utils/getCorrectedValue.ts b/src/utils/getCorrectedValue.ts index 4b61fad76..69615d7d4 100644 --- a/src/utils/getCorrectedValue.ts +++ b/src/utils/getCorrectedValue.ts @@ -1,7 +1,7 @@ // Publicodes's % unit is strangely handlded // the nodeValue is * 100 to account for the unit -import { NodeValue } from '@/publicodes-state/types' +import { NodeValue } from '@incubateur-ademe/nosgestesclimat' // hence we divide it by 100 and drop the unit export function getCorrectedValue({ diff --git a/tailwind.config.js b/tailwind.config.js index 82e9d0f8f..9ccd04fa6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -122,10 +122,16 @@ module.exports = { }, keyframes: { valuechange: { - '0%': { opacity: 0, transform: 'translateX(-10%)' }, - '20%': { opacity: 1 }, + '0%': { opacity: 0, transform: 'translateY(10px)' }, + '20%': { opacity: 0.2 }, '80%': { opacity: 1 }, - '100%': { opacity: 0, transform: 'translateX(10%)' }, + '100%': { opacity: 1, transform: 'translateY(0)' }, + }, + 'valuechange-reverse': { + '0%': { opacity: 0, transform: 'translateY(-14px)' }, + '20%': { opacity: 0.2 }, + '80%': { opacity: 1 }, + '100%': { opacity: 1, transform: 'translateY(0)' }, }, iconsRotation: { '0%': { transform: 'rotate(0deg)' }, @@ -200,7 +206,8 @@ module.exports = { }, }, animation: { - valuechange: 'valuechange 3s ease-out infinite', + valuechange: 'valuechange 0.5s ease-out forwards', + 'valuechange-reverse': 'valuechange-reverse 0.5s ease-out forwards', iconsRotation: 'iconsRotation 1s ease-in-out', 'rainbow-slow': 'rainbow 30s linear infinite', 'rainbow-fast': 'rainbow 5s linear infinite', @@ -248,189 +255,373 @@ module.exports = { 'border-transport-50', 'fill-transport-50', 'bg-transport-50', + 'hover:text-transport-50', + 'hover:border-transport-50', + 'hover:fill-transport-50', + 'hover:bg-transport-50', 'text-transport-100', 'border-transport-100', 'fill-transport-100', 'bg-transport-100', + 'hover:text-transport-100', + 'hover:border-transport-100', + 'hover:fill-transport-100', + 'hover:bg-transport-100', 'text-transport-200', 'border-transport-200', 'fill-transport-200', 'bg-transport-200', + 'hover:text-transport-200', + 'hover:border-transport-200', + 'hover:fill-transport-200', + 'hover:bg-transport-200', 'text-transport-300', 'border-transport-300', 'fill-transport-300', 'bg-transport-300', + 'hover:text-transport-300', + 'hover:border-transport-300', + 'hover:fill-transport-300', + 'hover:bg-transport-300', 'text-transport-400', 'border-transport-400', 'fill-transport-400', 'bg-transport-400', + 'hover:text-transport-400', + 'hover:border-transport-400', + 'hover:fill-transport-400', + 'hover:bg-transport-400', 'text-transport-500', 'border-transport-500', 'fill-transport-500', 'bg-transport-500', + 'hover:text-transport-500', + 'hover:border-transport-500', + 'hover:fill-transport-500', + 'hover:bg-transport-500', 'text-transport-600', 'border-transport-600', 'fill-transport-600', 'bg-transport-600', + 'hover:text-transport-600', + 'hover:border-transport-600', + 'hover:fill-transport-600', + 'hover:bg-transport-600', 'text-transport-700', 'border-transport-700', 'fill-transport-700', 'bg-transport-700', + 'hover:text-transport-700', + 'hover:border-transport-700', + 'hover:fill-transport-700', + 'hover:bg-transport-700', 'text-transport-800', 'border-transport-800', 'fill-transport-800', 'bg-transport-800', + 'hover:text-transport-800', + 'hover:border-transport-800', + 'hover:fill-transport-800', + 'hover:bg-transport-800', 'text-transport-900', 'border-transport-900', 'fill-transport-900', 'bg-transport-900', + 'hover:text-transport-900', + 'hover:border-transport-900', + 'hover:fill-transport-900', + 'hover:bg-transport-900', 'text-transport-950', 'border-transport-950', 'fill-transport-950', 'bg-transport-950', + 'hover:text-transport-950', + 'hover:border-transport-950', + 'hover:fill-transport-950', + 'hover:bg-transport-950', 'text-alimentation-100', 'border-alimentation-100', 'fill-alimentation-100', 'bg-alimentation-100', + 'hover:text-alimentation-100', + 'hover:border-alimentation-100', + 'hover:fill-alimentation-100', + 'hover:bg-alimentation-100', 'text-alimentation-50', 'border-alimentation-50', 'fill-alimentation-50', 'bg-alimentation-50', + 'hover:text-alimentation-50', + 'hover:border-alimentation-50', + 'hover:fill-alimentation-50', + 'hover:bg-alimentation-50', 'text-alimentation-200', 'border-alimentation-200', 'fill-alimentation-200', 'bg-alimentation-200', + 'hover:text-alimentation-200', + 'hover:border-alimentation-200', + 'hover:fill-alimentation-200', + 'hover:bg-alimentation-200', 'text-alimentation-300', 'border-alimentation-300', 'fill-alimentation-300', 'bg-alimentation-300', + 'hover:text-alimentation-300', + 'hover:border-alimentation-300', + 'hover:fill-alimentation-300', + 'hover:bg-alimentation-300', 'text-alimentation-400', 'border-alimentation-400', 'fill-alimentation-400', 'bg-alimentation-400', + 'hover:text-alimentation-400', + 'hover:border-alimentation-400', + 'hover:fill-alimentation-400', + 'hover:bg-alimentation-400', 'text-alimentation-500', 'border-alimentation-500', 'fill-alimentation-500', 'bg-alimentation-500', + 'hover:text-alimentation-500', + 'hover:border-alimentation-500', + 'hover:fill-alimentation-500', + 'hover:bg-alimentation-500', 'text-alimentation-600', 'border-alimentation-600', 'fill-alimentation-600', 'bg-alimentation-600', + 'hover:text-alimentation-600', + 'hover:border-alimentation-600', + 'hover:fill-alimentation-600', + 'hover:bg-alimentation-600', 'text-alimentation-700', 'border-alimentation-700', 'fill-alimentation-700', 'bg-alimentation-700', + 'hover:text-alimentation-700', + 'hover:border-alimentation-700', + 'hover:fill-alimentation-700', + 'hover:bg-alimentation-700', 'text-alimentation-800', 'border-alimentation-800', 'fill-alimentation-800', 'bg-alimentation-800', + 'hover:text-alimentation-800', + 'hover:border-alimentation-800', + 'hover:fill-alimentation-800', + 'hover:bg-alimentation-800', 'text-alimentation-900', 'border-alimentation-900', 'fill-alimentation-900', 'bg-alimentation-900', + 'hover:text-alimentation-900', + 'hover:border-alimentation-900', + 'hover:fill-alimentation-900', + 'hover:bg-alimentation-900', 'text-alimentation-950', 'border-alimentation-950', 'fill-alimentation-950', 'bg-alimentation-950', + 'hover:text-alimentation-950', + 'hover:border-alimentation-950', + 'hover:fill-alimentation-950', + 'hover:bg-alimentation-950', // Logement 'text-logement-50', 'border-logement-50', 'fill-logement-50', 'bg-logement-50', + 'hover:text-logement-50', + 'hover:border-logement-50', + 'hover:fill-logement-50', + 'hover:bg-logement-50', 'text-logement-100', 'border-logement-100', 'fill-logement-100', 'bg-logement-100', + 'hover:text-logement-100', + 'hover:border-logement-100', + 'hover:fill-logement-100', + 'hover:bg-logement-100', 'text-logement-200', 'border-logement-200', 'fill-logement-200', 'bg-logement-200', + 'hover:text-logement-200', + 'hover:border-logement-200', + 'hover:fill-logement-200', + 'hover:bg-logement-200', 'text-logement-300', 'border-logement-300', 'fill-logement-300', 'bg-logement-300', + 'hover:text-logement-300', + 'hover:border-logement-300', + 'hover:fill-logement-300', + 'hover:bg-logement-300', 'text-logement-400', 'border-logement-400', 'fill-logement-400', 'bg-logement-400', + 'hover:text-logement-400', + 'hover:border-logement-400', + 'hover:fill-logement-400', + 'hover:bg-logement-400', 'text-logement-500', 'border-logement-500', 'fill-logement-500', 'bg-logement-500', + 'hover:text-logement-500', + 'hover:border-logement-500', + 'hover:fill-logement-500', + 'hover:bg-logement-500', 'text-logement-600', 'border-logement-600', 'fill-logement-600', 'bg-logement-600', + 'hover:text-logement-600', + 'hover:border-logement-600', + 'hover:fill-logement-600', + 'hover:bg-logement-600', 'text-logement-700', 'border-logement-700', 'fill-logement-700', 'bg-logement-700', + 'hover:text-logement-700', + 'hover:border-logement-700', + 'hover:fill-logement-700', + 'hover:bg-logement-700', 'text-logement-800', 'border-logement-800', 'fill-logement-800', 'bg-logement-800', + 'hover:text-logement-800', + 'hover:border-logement-800', + 'hover:fill-logement-800', + 'hover:bg-logement-800', 'text-logement-900', 'border-logement-900', 'fill-logement-900', 'bg-logement-900', + 'hover:text-logement-900', + 'hover:border-logement-900', + 'hover:fill-logement-900', + 'hover:bg-logement-900', 'text-logement-950', 'border-logement-950', 'fill-logement-950', 'bg-logement-950', + 'hover:text-logement-950', + 'hover:border-logement-950', + 'hover:fill-logement-950', + 'hover:bg-logement-950', // Divers 'text-divers-50', 'border-divers-50', 'fill-divers-50', 'bg-divers-50', + 'hover:text-divers-50', + 'hover:border-divers-50', + 'hover:fill-divers-50', + 'hover:bg-divers-50', 'text-divers-100', 'border-divers-100', 'fill-divers-100', 'bg-divers-100', + 'hover:text-divers-100', + 'hover:border-divers-100', + 'hover:fill-divers-100', + 'hover:bg-divers-100', 'text-divers-200', 'border-divers-200', 'fill-divers-200', 'bg-divers-200', + 'hover:text-divers-200', + 'hover:border-divers-200', + 'hover:fill-divers-200', + 'hover:bg-divers-200', 'text-divers-300', 'border-divers-300', 'fill-divers-300', 'bg-divers-300', + 'hover:text-divers-300', + 'hover:border-divers-300', + 'hover:fill-divers-300', + 'hover:bg-divers-300', 'text-divers-400', 'border-divers-400', 'fill-divers-400', 'bg-divers-400', + 'hover:text-divers-400', + 'hover:border-divers-400', + 'hover:fill-divers-400', + 'hover:bg-divers-400', 'text-divers-500', 'border-divers-500', 'fill-divers-500', 'bg-divers-500', + 'hover:text-divers-500', + 'hover:border-divers-500', + 'hover:fill-divers-500', + 'hover:bg-divers-500', 'text-divers-600', 'border-divers-600', 'fill-divers-600', 'bg-divers-600', + 'hover:text-divers-600', + 'hover:border-divers-600', + 'hover:fill-divers-600', + 'hover:bg-divers-600', 'text-divers-700', 'border-divers-700', 'fill-divers-700', 'bg-divers-700', + 'hover:text-divers-700', + 'hover:border-divers-700', + 'hover:fill-divers-700', + 'hover:bg-divers-700', 'text-divers-800', 'border-divers-800', 'fill-divers-800', 'bg-divers-800', + 'hover:text-divers-800', + 'hover:border-divers-800', + 'hover:fill-divers-800', + 'hover:bg-divers-800', 'text-divers-900', 'border-divers-900', 'fill-divers-900', 'bg-divers-900', + 'hover:text-divers-900', + 'hover:border-divers-900', + 'hover:fill-divers-900', + 'hover:bg-divers-900', 'text-divers-950', 'border-divers-950', 'fill-divers-950', 'bg-divers-950', + 'hover:text-divers-950', + 'hover:border-divers-950', + 'hover:fill-divers-950', + 'hover:bg-divers-950', // Services sociétaux 'text-servicessocietaux-50', 'border-servicessocietaux-50', 'fill-servicessocietaux-50', 'bg-servicessocietaux-50', + 'hover:text-servicessocietaux-50', + 'hover:border-servicessocietaux-50', + 'hover:fill-servicessocietaux-50', + 'hover:bg-servicessocietaux-50', 'text-servicessocietaux-100', 'border-servicessocietaux-100', 'fill-servicessocietaux-100', 'bg-servicessocietaux-100', + 'hover:text-servicessocietaux-100', + 'hover:border-servicessocietaux-100', + 'hover:fill-servicessocietaux-100', + 'hover:bg-servicessocietaux-100', 'text-servicessocietaux-200', 'border-servicessocietaux-200', 'fill-servicessocietaux-200', diff --git a/tsconfig.json b/tsconfig.json index e336ecf90..f03d41eff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ES6", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -23,22 +19,15 @@ } ], "paths": { - "@/*": [ - "./src/*" - ] + "@/*": ["./src/*"] }, "forceConsistentCasingInFileNames": true }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - "netlify/functions/**/*.ts" - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": [ "node_modules", "cypress/**/*", - "scripts/**/*" + "scripts/**/*", + "./next.config.js" ] } diff --git a/yarn.lock b/yarn.lock index 8894c8072..133001dc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41,6 +41,32 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== +"@babel/compat-data@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" + integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.18.5": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" @@ -112,6 +138,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.0", "@babel/generator@^7.7.2": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== + dependencies: + "@babel/types" "^7.25.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -134,6 +170,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -213,6 +260,21 @@ "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -252,6 +314,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" @@ -272,6 +339,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + "@babel/helpers@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" @@ -289,6 +361,14 @@ "@babel/template" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/highlight@^7.24.2": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" @@ -309,6 +389,13 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" + integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== + dependencies: + "@babel/types" "^7.25.2" + "@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" @@ -319,6 +406,125 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" + integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" + integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.1", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" @@ -351,6 +557,15 @@ "@babel/parser" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/template@^7.25.0", "@babel/template@^7.3.3": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/traverse@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" @@ -383,6 +598,28 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.2": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" + integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.2" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.3.3": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" @@ -401,6 +638,11 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -697,6 +939,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@faker-js/faker@^8.4.1": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" + integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== + "@floating-ui/core@^1.0.0": version "1.6.1" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.1.tgz#a4e6fef1b069cda533cbc7a4998c083a37f37573" @@ -776,10 +1023,89 @@ prompt-sync "^4.2.0" yargs "^17.7.2" -"@incubateur-ademe/nosgestesclimat@3.0.0-rc.3": - version "3.0.0-rc.3" - resolved "https://registry.yarnpkg.com/@incubateur-ademe/nosgestesclimat/-/nosgestesclimat-3.0.0-rc.3.tgz#467494696f25a990eecf133f94f0d0f0e46ae6e7" - integrity sha512-cFsdyoYBD0HpqYtUN5Ajwg1j2hRIwnX+9WlbfHaMXkMihXD8GSnn3s3iLetp4gYGetZuHOEOzyHrDKdBS991kA== +"@incubateur-ademe/nosgestesclimat@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@incubateur-ademe/nosgestesclimat/-/nosgestesclimat-3.1.0.tgz#9b1e7763c9d99eba487710445bf1c8f0954268d0" + integrity sha512-OtvKm3xD/dtr03YHGsveZAcZJfIxlHu92GeUJTkyVs5OwhPUeSJIZZj/v6qKC6u8Smz33OwlILSZYQTTZrochw== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" "@jest/expect-utils@^29.7.0": version "29.7.0" @@ -788,6 +1114,66 @@ dependencies: jest-get-type "^29.6.3" +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -795,6 +1181,56 @@ dependencies: "@sinclair/typebox" "^0.27.8" +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -847,7 +1283,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -906,10 +1342,10 @@ dependencies: webpack-bundle-analyzer "4.10.1" -"@next/env@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.5.tgz#1d9328ab828711d3517d0a1d505acb55e5ef7ad0" - integrity sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA== +"@next/env@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.7.tgz#40fcd6ccdd53fd7e6788a0604f39032c84bea112" + integrity sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ== "@next/eslint-plugin-next@14.1.3": version "14.1.3" @@ -918,57 +1354,57 @@ dependencies: glob "10.3.10" -"@next/mdx@^14.0.2": - version "14.2.3" - resolved "https://registry.yarnpkg.com/@next/mdx/-/mdx-14.2.3.tgz#f914cc8c2caf05cbf2ed10eff3a79977faa3eced" - integrity sha512-oVz7BWpoLQ9dKvCKxPIX9X6BILPTrpTJnYDn2lAsZvK7J9Ela6xNm57vNwgZ8q7xw1THSDdSlbPNgIalM7U/+A== +"@next/mdx@^14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/mdx/-/mdx-14.2.7.tgz#13c07b501b07059433eca76499dda512c05071a5" + integrity sha512-GeWQODkqa+0fWCVoc6fMZDXsxbsg386EqC+be9/UreJn/TTz/RWN2ZMHcRHY+sjLkOzfOAsRUjEdPckl10+qCg== dependencies: source-map "^0.7.0" -"@next/swc-darwin-arm64@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz#d0a160cf78c18731c51cc0bff131c706b3e9bb05" - integrity sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ== - -"@next/swc-darwin-x64@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz#eb832a992407f6e6352eed05a073379f1ce0589c" - integrity sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA== - -"@next/swc-linux-arm64-gnu@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz#098fdab57a4664969bc905f5801ef5a89582c689" - integrity sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA== - -"@next/swc-linux-arm64-musl@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz#243a1cc1087fb75481726dd289c7b219fa01f2b5" - integrity sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA== - -"@next/swc-linux-x64-gnu@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz#b8a2e436387ee4a52aa9719b718992e0330c4953" - integrity sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ== - -"@next/swc-linux-x64-musl@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz#cb8a9adad5fb8df86112cfbd363aab5c6d32757b" - integrity sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ== - -"@next/swc-win32-arm64-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz#81f996c1c38ea0900d4e7719cc8814be8a835da0" - integrity sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw== - -"@next/swc-win32-ia32-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz#f61c74ce823e10b2bc150e648fc192a7056422e0" - integrity sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg== - -"@next/swc-win32-x64-msvc@14.2.5": - version "14.2.5" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz#ed199a920efb510cfe941cd75ed38a7be21e756f" - integrity sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g== +"@next/swc-darwin-arm64@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.7.tgz#6cd39ba5d5f43705de44e389d4b4f5d2df391927" + integrity sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw== + +"@next/swc-darwin-x64@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.7.tgz#a1d191a293443cf8df9451b8f13a348caa718cb7" + integrity sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA== + +"@next/swc-linux-arm64-gnu@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.7.tgz#9da3f993b3754b900fe7b469de51898fc51112f2" + integrity sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ== + +"@next/swc-linux-arm64-musl@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.7.tgz#f75662bdedd2d91ad7e05778274fa17659f1f02f" + integrity sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw== + +"@next/swc-linux-x64-gnu@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.7.tgz#3c6c5b551a5af4fc8178bd5733c8063266034e79" + integrity sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw== + +"@next/swc-linux-x64-musl@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.7.tgz#16f92f00263d1fce91ae80e5f230eb1feea484e4" + integrity sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ== + +"@next/swc-win32-arm64-msvc@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.7.tgz#1224cb8a04cd9caad785a2187df9e85b49414a42" + integrity sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ== + +"@next/swc-win32-ia32-msvc@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.7.tgz#9494aaf9cc50ddef600f8c1b2ed0f216b19f9294" + integrity sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w== + +"@next/swc-win32-x64-msvc@14.2.7": + version "14.2.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.7.tgz#75e1d90758cb10a547e1cdfb878871da28123682" + integrity sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA== "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -1620,6 +2056,20 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@socialgouv/react-departements@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@socialgouv/react-departements/-/react-departements-3.0.0.tgz#4ab8e65f1164d9bcc1d5aa61aae25dcbd51a8974" @@ -1627,6 +2077,75 @@ dependencies: react-svgmt "^1.1.9" +"@swc/core-darwin-arm64@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.14.tgz#a4530ec755ea183802cc9dfe4900ab5f6a327fea" + integrity sha512-V0OUXjOH+hdGxDYG8NkQzy25mKOpcNKFpqtZEzLe5V/CpLJPnpg1+pMz70m14s9ZFda9OxsjlvPbg1FLUwhgIQ== + +"@swc/core-darwin-x64@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.7.14.tgz#2c9c717fd28dd1dde9c21cf58b01f1cda7976b1a" + integrity sha512-9iFvUnxG6FC3An5ogp5jbBfQuUmTTwy8KMB+ZddUoPB3NR1eV+Y9vOh/tfWcenSJbgOKDLgYC5D/b1mHAprsrQ== + +"@swc/core-linux-arm-gnueabihf@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.14.tgz#fed055c9c65347177c8df88720f8a51793a4df06" + integrity sha512-zGJsef9qPivKSH8Vv4F/HiBXBTHZ5Hs3ZjVGo/UIdWPJF8fTL9OVADiRrl34Q7zOZEtGXRwEKLUW1SCQcbDvZA== + +"@swc/core-linux-arm64-gnu@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.14.tgz#ca740c8ea26f041b2dc43ba87facec452052814f" + integrity sha512-AxV3MPsoI7i4B8FXOew3dx3N8y00YoJYvIPfxelw07RegeCEH3aHp2U2DtgbP/NV1ugZMx0TL2Z2DEvocmA51g== + +"@swc/core-linux-arm64-musl@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.14.tgz#fbc6fed24f5ad58b948e5b7abe6cd1f07112bef1" + integrity sha512-JDLdNjUj3zPehd4+DrQD8Ltb3B5lD8D05IwePyDWw+uR/YPc7w/TX1FUVci5h3giJnlMCJRvi1IQYV7K1n7KtQ== + +"@swc/core-linux-x64-gnu@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.14.tgz#509a37833e4fbf89506b9291d9bd131fa2017fca" + integrity sha512-Siy5OvPCLLWmMdx4msnEs8HvEVUEigSn0+3pbLjv78iwzXd0qSBNHUPZyC1xeurVaUbpNDxZTpPRIwpqNE2+Og== + +"@swc/core-linux-x64-musl@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.14.tgz#81156cc6ff814ad4b8fcf6eb6658d3f247db0b57" + integrity sha512-FtEGm9mwtRYQNK43WMtUIadxHs/ja2rnDurB99os0ZoFTGG2IHuht2zD97W0wB8JbqEabT1XwSG9Y5wmN+ciEQ== + +"@swc/core-win32-arm64-msvc@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.14.tgz#c605fa783b5fbe1fff784ace4c4bb074b8d6026d" + integrity sha512-Jp8KDlfq7Ntt2/BXr0y344cYgB1zf0DaLzDZ1ZJR6rYlAzWYSccLYcxHa97VGnsYhhPspMpmCvHid97oe2hl4A== + +"@swc/core-win32-ia32-msvc@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.14.tgz#3e15dc3b662c9fab851a38b3e271c8e2da4ba03a" + integrity sha512-I+cFsXF0OU0J9J4zdWiQKKLURO5dvCujH9Jr8N0cErdy54l9d4gfIxdctfTF+7FyXtWKLTCkp+oby9BQhkFGWA== + +"@swc/core-win32-x64-msvc@1.7.14": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.14.tgz#83958d92e9f07865ec9365212111fbc295660f0d" + integrity sha512-NNrprQCK6d28mG436jVo2TD+vACHseUECacEBGZ9Ef0qfOIWS1XIt2MisQKG0Oea2VvLFl6tF/V4Lnx/H0Sn3Q== + +"@swc/core@^1.7.12": + version "1.7.14" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.7.14.tgz#d10492b5a4168cb1e73cf561a315e8b0f62255ed" + integrity sha512-9aeXeifnyuvc2pcuuhPQgVUwdpGEzZ+9nJu0W8/hNl/aESFsJGR5i9uQJRGu0atoNr01gK092fvmqMmQAPcKow== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.12" + optionalDependencies: + "@swc/core-darwin-arm64" "1.7.14" + "@swc/core-darwin-x64" "1.7.14" + "@swc/core-linux-arm-gnueabihf" "1.7.14" + "@swc/core-linux-arm64-gnu" "1.7.14" + "@swc/core-linux-arm64-musl" "1.7.14" + "@swc/core-linux-x64-gnu" "1.7.14" + "@swc/core-linux-x64-musl" "1.7.14" + "@swc/core-win32-arm64-msvc" "1.7.14" + "@swc/core-win32-ia32-msvc" "1.7.14" + "@swc/core-win32-x64-msvc" "1.7.14" + "@swc/counter@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" @@ -1640,17 +2159,38 @@ "@swc/counter" "^0.1.3" tslib "^2.4.0" -"@tanstack/query-core@5.32.0": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.32.0.tgz#e097ec2b394a2f64de33c98cd8baf3525c99641a" - integrity sha512-Z3flEgCat55DRXU5UMwYU1U+DgFZKA3iufyOKs+II7iRAo0uXkeU7PH5e6sOH1CGEag0IpKmZxlUFpCg6roSKw== +"@swc/jest@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + +"@swc/types@^0.1.12": + version "0.1.12" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.12.tgz#7f632c06ab4092ce0ebd046ed77ff7557442282f" + integrity sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA== + dependencies: + "@swc/counter" "^0.1.3" + +"@tanstack/query-core@5.52.2": + version "5.52.2" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.52.2.tgz#a023864a892fda9858b724d667eb19cd84ce054a" + integrity sha512-9vvbFecK4A0nDnrc/ks41e3UHONF1DAnGz8Tgbxkl59QcvKWmc0ewhYuIKRh8NC4ja5LTHT9EH16KHbn2AIYWA== "@tanstack/react-query@^5.28.4": - version "5.32.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.32.0.tgz#52d441e7ad2a0098dc426f3834f68150c13f265b" - integrity sha512-+E3UudQtarnx9A6xhpgMZapyF+aJfNBGFMgI459FnduEZqT/9KhOWnMOneZahLRt52yzskSA0AuOyLkXHK0yBA== + version "5.52.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.52.2.tgz#3fffbc86351edcaeec335bc8958bcab4204bd169" + integrity sha512-d4OwmobpP+6+SvuAxW1RzAY95Pv87Gu+0GjtErzFOUXo+n0FGcwxKvzhswCsXKxsgnAr3bU2eJ2u+GXQAutkCQ== dependencies: - "@tanstack/query-core" "5.32.0" + "@tanstack/query-core" "5.52.2" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@tsconfig/node10@^1.0.7": version "1.0.11" @@ -1686,6 +2226,39 @@ dependencies: "@types/estree" "*" +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1829,6 +2402,13 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + "@types/hast@^3.0.0": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" @@ -1846,7 +2426,7 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -1874,7 +2454,7 @@ "@types/pixelmatch" "*" ssim.js "^3.1.1" -"@types/jest@*": +"@types/jest@*", "@types/jest@^29.5.12": version "29.5.12" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== @@ -1882,6 +2462,15 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + "@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -2132,6 +2721,11 @@ resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.2.tgz#51b1c00b516a5774ada5d611e65eb123f988ef8d" integrity sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA== +"@types/tough-cookie@*": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== + "@types/unist@*", "@types/unist@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20" @@ -2443,6 +3037,19 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + acorn-import-assertions@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" @@ -2463,11 +3070,23 @@ acorn-walk@^8.0.0, acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== +acorn-walk@^8.0.2: + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" + acorn@^8.0.0, acorn@^8.0.4, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.1.0, acorn@^8.11.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + acorn@^8.8.1: version "8.12.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" @@ -2521,7 +3140,7 @@ ansi-colors@^4.1.1, ansi-colors@^4.1.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== -ansi-escapes@^4.3.0: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -2562,7 +3181,7 @@ any-promise@^1.0.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== -anymatch@^3.1.3, anymatch@~3.1.2: +anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -2800,6 +3419,40 @@ axobject-query@^3.2.1: dependencies: dequal "^2.0.3" +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -2809,6 +3462,35 @@ babel-plugin-macros@^3.1.0: cosmiconfig "^7.0.0" resolve "^1.19.0" +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -2934,6 +3616,23 @@ browserslist@^4.21.10, browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +browserslist@^4.23.1: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -2993,6 +3692,16 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + camelize@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" @@ -3003,6 +3712,11 @@ caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.300015 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz#f894b4209376a0bf923d67d9c361d96b1dfebe39" integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog== +caniuse-lite@^1.0.30001646: + version "1.0.30001651" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -3046,6 +3760,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + character-entities-html4@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" @@ -3121,7 +3840,7 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -cjs-module-lexer@^1.2.2: +cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: version "1.3.1" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== @@ -3196,6 +3915,11 @@ clsx@^2.0.0, clsx@^2.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + codepage@~1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab" @@ -3206,6 +3930,11 @@ collapse-white-space@^2.0.0: resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -3343,12 +4072,25 @@ crc-32@~1.2.0, crc-32@~1.2.1: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3397,6 +4139,23 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" @@ -3574,6 +4333,15 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" @@ -3642,6 +4410,11 @@ decimal.js-light@^2.4.1: resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== +decimal.js@^10.4.2: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" @@ -3649,6 +4422,11 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -3664,6 +4442,11 @@ deepl-node@^1.10.2: form-data "^3.0.0" loglevel ">=1.6.2" +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -3692,6 +4475,11 @@ dequal@^2.0.0, dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + devlop@^1.0.0, devlop@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" @@ -3762,6 +4550,13 @@ domelementtype@^2.3.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" @@ -3801,6 +4596,16 @@ electron-to-chromium@^1.4.668: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.750.tgz#d278a619af727ed069de1317115187282b1131ee" integrity sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA== +electron-to-chromium@^1.5.4: + version "1.5.13" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" + integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -4016,7 +4821,7 @@ esbuild@^0.20.1: "@esbuild/win32-ia32" "0.20.2" "@esbuild/win32-x64" "0.20.2" -escalade@^3.1.1: +escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== @@ -4036,6 +4841,17 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + eslint-config-next@14.1.3: version "14.1.3" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.1.3.tgz#f5c75c088c5df35da2a02129cbf5dec9defb3f13" @@ -4250,7 +5066,7 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -4365,6 +5181,21 @@ execa@4.1.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -4377,7 +5208,12 @@ exenv@^1.2.0: resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== -expect@^29.0.0: +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== @@ -4447,7 +5283,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -4464,6 +5300,13 @@ fastq@^1.13.0, fastq@^1.6.0: dependencies: reusify "^1.0.4" +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -4497,6 +5340,14 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -4651,7 +5502,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -4707,6 +5558,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-stdin@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" @@ -4719,6 +5575,11 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -4815,7 +5676,7 @@ glob@^10.4.1: minipass "^7.1.2" path-scurry "^1.11.1" -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5044,7 +5905,14 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: dependencies: react-is "^16.7.0" -html-escaper@^2.0.2: +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +html-escaper@^2.0.0, html-escaper@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== @@ -5071,6 +5939,15 @@ htmlparser2@^8.0.1: domutils "^3.0.1" entities "^4.4.0" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -5080,7 +5957,7 @@ http-signature@~1.3.6: jsprim "^2.0.2" sshpk "^1.14.1" -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -5093,6 +5970,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + humanize-duration@^3.27.3: version "3.32.0" resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.32.0.tgz#b25f64ef55d723b049b197b6b4aa3c96c202b6c9" @@ -5143,7 +6025,7 @@ i18next@^23.5.1, i18next@^23.7.6: dependencies: "@babel/runtime" "^7.23.2" -iconv-lite@^0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -5188,6 +6070,14 @@ import-in-the-middle@^1.8.1: cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -5355,6 +6245,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-generator-function@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" @@ -5424,6 +6319,11 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-reference@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -5547,6 +6447,59 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" @@ -5563,14 +6516,94 @@ jackspeak@2.1.1, jackspeak@^2.3.5, jackspeak@^2.3.6, jackspeak@^3.1.2: resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd" integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw== dependencies: - cliui "^8.0.1" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" + cliui "^8.0.1" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +javascript-stringify@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" + integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" -javascript-stringify@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" - integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" jest-diff@^29.7.0: version "29.7.0" @@ -5582,11 +6615,74 @@ jest-diff@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + jest-image-snapshot@^6.1.0: version "6.4.0" resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-6.4.0.tgz#65831d13beb1680f3bba9fb28230fa53d76939be" @@ -5601,6 +6697,14 @@ jest-image-snapshot@^6.1.0: rimraf "^2.6.2" ssim.js "^3.1.1" +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-matcher-utils@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" @@ -5626,6 +6730,129 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" @@ -5638,6 +6865,32 @@ jest-util@^29.7.0: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -5647,6 +6900,26 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + jiti@^1.19.1, jiti@^1.20.0: version "1.21.0" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" @@ -5677,6 +6950,38 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -5734,6 +7039,11 @@ json5@^2.1.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5787,6 +7097,11 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -5809,6 +7124,11 @@ lead@^4.0.0: resolved "https://registry.yarnpkg.com/lead/-/lead-4.0.0.tgz#5317a49effb0e7ec3a0c8fb9c1b24fb716aab939" integrity sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg== +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -5860,6 +7180,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -5955,11 +7282,25 @@ magic-string@^0.30.3: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + markdown-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" @@ -6542,12 +7883,12 @@ next-i18n-router@^4.1.1: "@formatjs/intl-localematcher" "^0.2.32" negotiator "^0.6.3" -next@^14.2.5: - version "14.2.5" - resolved "https://registry.yarnpkg.com/next/-/next-14.2.5.tgz#afe4022bb0b752962e2205836587a289270efbea" - integrity sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA== +next@^14.2.7: + version "14.2.7" + resolved "https://registry.yarnpkg.com/next/-/next-14.2.7.tgz#e02d5d9622ff4b998e5c89adfd660c9bf6435970" + integrity sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ== dependencies: - "@next/env" "14.2.5" + "@next/env" "14.2.7" "@swc/helpers" "0.5.5" busboy "1.6.0" caniuse-lite "^1.0.30001579" @@ -6555,15 +7896,15 @@ next@^14.2.5: postcss "8.4.31" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-darwin-arm64" "14.2.5" - "@next/swc-darwin-x64" "14.2.5" - "@next/swc-linux-arm64-gnu" "14.2.5" - "@next/swc-linux-arm64-musl" "14.2.5" - "@next/swc-linux-x64-gnu" "14.2.5" - "@next/swc-linux-x64-musl" "14.2.5" - "@next/swc-win32-arm64-msvc" "14.2.5" - "@next/swc-win32-ia32-msvc" "14.2.5" - "@next/swc-win32-x64-msvc" "14.2.5" + "@next/swc-darwin-arm64" "14.2.7" + "@next/swc-darwin-x64" "14.2.7" + "@next/swc-linux-arm64-gnu" "14.2.7" + "@next/swc-linux-arm64-musl" "14.2.7" + "@next/swc-linux-x64-gnu" "14.2.7" + "@next/swc-linux-x64-musl" "14.2.7" + "@next/swc-win32-arm64-msvc" "14.2.7" + "@next/swc-win32-ia32-msvc" "14.2.7" + "@next/swc-win32-x64-msvc" "14.2.7" node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" @@ -6572,11 +7913,21 @@ node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -6594,7 +7945,7 @@ now-and-later@^3.0.0: dependencies: once "^1.4.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -6608,6 +7959,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +nwsapi@^2.2.2: + version "2.2.12" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.12.tgz#fb6af5c0ec35b27b4581eb3bbad34ec9e5c696f8" + integrity sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w== + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6696,7 +8052,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -6734,13 +8090,27 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== -p-limit@^3.0.2: +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -6755,6 +8125,11 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -6794,7 +8169,7 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" -parse5@^7.0.0: +parse5@^7.0.0, parse5@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== @@ -6915,6 +8290,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -6925,7 +8305,7 @@ pify@^2.2.0, pify@^2.3.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== -pirates@^4.0.1: +pirates@^4.0.1, pirates@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== @@ -6937,6 +8317,13 @@ pixelmatch@^5.1.0: dependencies: pngjs "^6.0.0" +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + platform@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461" @@ -7147,6 +8534,14 @@ prompt-sync@^4.2.0: dependencies: strip-ansi "^5.0.0" +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -7199,6 +8594,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + qrcode.react@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" @@ -7335,6 +8735,11 @@ react-move@^2.7.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-number-format@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.1.tgz#ca191af06c4618a823874efa486df50a1abbfc18" + integrity sha512-NICOjo/70dcAiwVmH6zMWoZrTQDlBrEXV/f7S0t/ewlpzp4z00pasg5G1yBX6NHLafwOF3QZ+VvK/XApwSKxdA== + react-select@^5.8.0: version "5.8.0" resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.8.0.tgz#bd5c467a4df223f079dd720be9498076a3f085b5" @@ -7560,11 +8965,23 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-options@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-2.0.0.tgz#a1a57a9949db549dd075de3f5550675f02f1e4c5" @@ -7577,7 +8994,12 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@1.22.8, resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4: +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@1.22.8, resolve@^1.1.7, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -7692,6 +9114,13 @@ safe-regex-test@^1.0.3: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.23.0: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -7716,7 +9145,7 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -7794,7 +9223,7 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" -signal-exit@^3.0.2: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -7813,6 +9242,11 @@ sirv@^2.0.3: mrmime "^2.0.0" totalist "^3.0.0" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -7848,6 +9282,14 @@ source-map-js@^1.0.2, source-map-js@^1.1.0, source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -7861,7 +9303,7 @@ source-map@^0.5.7: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -7949,6 +9391,14 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: optionalDependencies: bare-events "^2.2.0" +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + string-replace-to-array@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/string-replace-to-array/-/string-replace-to-array-2.1.0.tgz#44571dbd33a3e23de31db948b5b84f1b7913fb39" @@ -8055,6 +9505,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -8150,6 +9605,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0, symlink-or-copy@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe" @@ -8223,6 +9683,15 @@ terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -8270,6 +9739,11 @@ tmp@~0.2.3: resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -8294,6 +9768,16 @@ totalist@^3.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== +tough-cookie@^4.1.2: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tough-cookie@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" @@ -8304,6 +9788,13 @@ tough-cookie@^4.1.3: universalify "^0.2.0" url-parse "^1.5.3" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -8387,6 +9878,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -8578,6 +10074,14 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -8630,6 +10134,15 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + value-or-function@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-4.0.0.tgz#70836b6a876a010dc3a2b884e7902e9db064378d" @@ -8745,6 +10258,13 @@ vue-template-compiler@^2.6.11: de-indent "^1.0.2" he "^1.2.0" +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" + walk-sync@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.2.0.tgz#80786b0657fcc8c0e1c0b1a042a09eae2966387a" @@ -8755,6 +10275,13 @@ walk-sync@^2.2.0: matcher-collection "^2.0.0" minimatch "^3.0.4" +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" @@ -8775,6 +10302,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-bundle-analyzer@4.10.1: version "4.10.1" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454" @@ -8834,11 +10366,31 @@ webpack@^5: watchpack "^2.4.1" webpack-sources "^3.2.3" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-fetch@^3.4.1: version "3.6.20" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -8942,11 +10494,24 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + ws@^7.3.1: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.11.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xlsx@^0.18.5: version "0.18.5" resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0" @@ -8960,6 +10525,16 @@ xlsx@^0.18.5: wmf "~1.0.1" word "~0.3.0" +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -9004,7 +10579,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.7.2: +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==