diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1b189..09fd21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ and this project adheres to ### Fixed +- 🐛(front) resolve React hydration errors - 🚑️(user) allow longer short names #182 - ## [0.0.9] - 2025-11-17 ### Added diff --git a/src/frontend/apps/conversations/package.json b/src/frontend/apps/conversations/package.json index 4659c43..001c5b6 100644 --- a/src/frontend/apps/conversations/package.json +++ b/src/frontend/apps/conversations/package.json @@ -36,6 +36,7 @@ "i18next-browser-languagedetector": "8.1.0", "idb": "8.0.3", "lodash": "4.17.21", + "lottie-react": "^2.4.1", "luxon": "3.6.1", "micromark-extension-llm-math": "3.1.1-20250610", "next": "15.3.3", @@ -45,7 +46,6 @@ "react-dom": "18.3.1", "react-i18next": "15.5.2", "react-intersection-observer": "9.16.0", - "react-lottie": "^1.2.10", "react-markdown": "10.1.0", "react-select": "5.10.1", "rehype-katex": "7.0.1", diff --git a/src/frontend/apps/conversations/src/components/DropdownMenu.tsx b/src/frontend/apps/conversations/src/components/DropdownMenu.tsx index 6d02f56..8ca682f 100644 --- a/src/frontend/apps/conversations/src/components/DropdownMenu.tsx +++ b/src/frontend/apps/conversations/src/components/DropdownMenu.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, useRef, useState } from 'react'; +import { PropsWithChildren, useEffect, useRef, useState } from 'react'; import { css } from 'styled-components'; import { Box, BoxButton, BoxProps, DropButton, Icon, Text } from '@/components'; @@ -35,8 +35,16 @@ export const DropdownMenu = ({ }: PropsWithChildren) => { const { spacingsTokens, colorsTokens } = useCunninghamTheme(); const [isOpen, setIsOpen] = useState(false); + const [buttonWidth, setButtonWidth] = useState(undefined); const blockButtonRef = useRef(null); + useEffect(() => { + // Mettre à jour la largeur uniquement côté client + if (blockButtonRef.current) { + setButtonWidth(blockButtonRef.current.clientWidth); + } + }, [isOpen]); + const onOpenChange = (isOpen: boolean) => { setIsOpen(isOpen); }; @@ -76,7 +84,7 @@ export const DropdownMenu = ({ > {topMessage && ( diff --git a/src/frontend/apps/conversations/src/components/Loader.tsx b/src/frontend/apps/conversations/src/components/Loader.tsx index b262b78..609d0d0 100644 --- a/src/frontend/apps/conversations/src/components/Loader.tsx +++ b/src/frontend/apps/conversations/src/components/Loader.tsx @@ -1,20 +1,17 @@ -import Lottie from 'react-lottie'; +import dynamic from 'next/dynamic'; +const Lottie = dynamic(() => import('lottie-react'), { ssr: false }); import searchingAnimation from '@/assets/lotties/searching'; -export const Loader = () => { - const LoaderOptions = { - loop: true, - autoplay: true, - animationData: searchingAnimation, - rendererSettings: { - preserveAspectRatio: 'xMidYMid slice', - }, - } as const; - +export function Loader() { return ( -
- +
+
); -}; +} diff --git a/src/frontend/apps/conversations/src/components/ToastProvider.tsx b/src/frontend/apps/conversations/src/components/ToastProvider.tsx index 3ff584d..f6745fa 100644 --- a/src/frontend/apps/conversations/src/components/ToastProvider.tsx +++ b/src/frontend/apps/conversations/src/components/ToastProvider.tsx @@ -3,6 +3,7 @@ import { createContext, useCallback, useContext, + useEffect, useState, } from 'react'; import { createPortal } from 'react-dom'; @@ -47,6 +48,11 @@ interface ToastProviderProps { export const ToastProvider = ({ children }: ToastProviderProps) => { const [toasts, setToasts] = useState([]); + const [isMounted, setIsMounted] = useState(false); + + useEffect(() => { + setIsMounted(true); + }, []); const showToast = useCallback( ( @@ -83,7 +89,9 @@ export const ToastProvider = ({ children }: ToastProviderProps) => { return ( {children} - {typeof window !== 'undefined' && + {isMounted && + typeof document !== 'undefined' && + document.body && createPortal( import('@/components').then((mod) => ({ default: mod.ToastProvider })), + { ssr: false, loading: () => null }, +); + +const CunninghamProviderNoSSR = dynamic( + () => + import('@openfun/cunningham-react').then((mod) => ({ + default: mod.CunninghamProvider, + })), + { ssr: false }, +); -/** - * QueryClient: - * - defaultOptions: - * - staleTime: - * - global cache duration - we decided 3 minutes - * - It can be overridden to each query - */ -const defaultOptions = { - queries: { - staleTime: 1000 * 60 * 3, - retry: 1, - }, -}; const queryClient = new QueryClient({ - defaultOptions, + defaultOptions: { + queries: { staleTime: 1000 * 60 * 3, retry: 1 }, + mutations: { + onError: (error) => { + if ( + error instanceof Error && + 'status' in error && + error.status === 401 + ) { + void queryClient.resetQueries({ queryKey: [KEY_AUTH] }); + setAuthUrl(); + if (typeof window !== 'undefined') { + window.location.href = '/401'; + } + } + }, + }, + }, }); export function AppProvider({ children }: { children: React.ReactNode }) { - const { theme } = useCunninghamTheme(); - const { replace } = useRouter(); - - const initializeResizeListener = useResponsiveStore( - (state) => state.initializeResizeListener, - ); + const theme = useCunninghamTheme((state) => state.theme); useEffect(() => { - return initializeResizeListener(); - }, [initializeResizeListener]); - - useEffect(() => { - queryClient.setDefaultOptions({ - ...defaultOptions, - mutations: { - onError: (error) => { - if ( - error instanceof Error && - 'status' in error && - error.status === 401 - ) { - void queryClient.resetQueries({ - queryKey: [KEY_AUTH], - }); - setAuthUrl(); - void replace(`/401`); - } - }, - }, - }); - }, [replace]); + return useResponsiveStore.getState().initializeResizeListener(); + }, []); return ( - + - + {children} - + - + ); } diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 68e6d3b..4656d9d 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -6451,14 +6451,6 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - bail@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz" @@ -7004,11 +6996,6 @@ core-js-compat@^3.40.0: dependencies: browserslist "^4.24.4" -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - core-js@^3.0.0, core-js@^3.38.1: version "3.42.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz" @@ -10097,7 +10084,14 @@ loose-envify@^1.0.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lottie-web@^5.12.2: +lottie-react@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/lottie-react/-/lottie-react-2.4.1.tgz#4bd3f2a8a5e48edbd43c05ca5080fdd50f049d31" + integrity sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw== + dependencies: + lottie-web "^5.10.2" + +lottie-web@^5.10.2: version "5.13.0" resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.13.0.tgz#441d3df217cc8ba302338c3f168e1a3af0f221d3" integrity sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ== @@ -11428,7 +11422,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +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.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -11825,15 +11819,6 @@ react-lifecycles-compat@^3.0.0: resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-lottie@^1.2.10: - version "1.2.10" - resolved "https://registry.yarnpkg.com/react-lottie/-/react-lottie-1.2.10.tgz#399f78a448a7833b2380d74fc489ecf15f8d18c7" - integrity sha512-x0eWX3Z6zSx1XM5QSjnLupc6D22LlMCB0PH06O/N/epR2hsLaj1Vxd9RtMnbbEHjJ/qlsgHJ6bpN3vnZI92hjw== - dependencies: - babel-runtime "^6.26.0" - lottie-web "^5.12.2" - prop-types "^15.6.1" - react-markdown@10.1.0: version "10.1.0" resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz" @@ -12101,11 +12086,6 @@ regenerate@^1.4.2: resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - regex-recursion@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-6.0.2.tgz#a0b1977a74c87f073377b938dbedfab2ea582b33"