diff --git a/console/src/components/InstallPatternPage.tsx b/console/src/components/InstallPatternPage.tsx index ed288490e..0112e2889 100644 --- a/console/src/components/InstallPatternPage.tsx +++ b/console/src/components/InstallPatternPage.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import { useHistory, useRouteMatch } from 'react-router-dom'; +import { useNavigateCompat } from '../hooks/useNavigateCompat'; +import { useParamsCompat } from '../hooks/useParamsCompat'; import { ActionGroup, Alert, @@ -56,9 +57,8 @@ const PatternModel = { export default function InstallPatternPage() { const { t } = useTranslation('plugin__patterns-operator-console-plugin'); - const history = useHistory(); - const match = useRouteMatch<{ name: string }>('/patterns/install/:name'); - const name = match?.params?.name; + const navigate = useNavigateCompat(); + const { name } = useParamsCompat('/patterns/install/:name'); // Secret form state (integrated inline instead of separate page) @@ -251,11 +251,11 @@ export default function InstallPatternPage() { if (hasSecrets && vaultJobStatus?.status !== 'succeeded') return; const timer = setTimeout(() => { - history.push('/patterns'); + navigate('/patterns'); }, 3000); return () => clearTimeout(timer); - }, [patternStatus, history, secretTemplate, secretFormData, vaultJobStatus]); + }, [patternStatus, navigate, secretTemplate, secretFormData, vaultJobStatus]); const handleSubmit = async () => { console.log('🚀 [InstallPatternPage] Starting pattern installation process'); @@ -410,7 +410,7 @@ export default function InstallPatternPage() { return t('Your pattern has been created. The operator is now reconciling it.'); })()}

- @@ -612,7 +612,7 @@ export default function InstallPatternPage() { > {t('Install')} - diff --git a/console/src/components/ManageSecretsPage.tsx b/console/src/components/ManageSecretsPage.tsx index ed22cf551..83b6dc507 100644 --- a/console/src/components/ManageSecretsPage.tsx +++ b/console/src/components/ManageSecretsPage.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import { useHistory, useRouteMatch } from 'react-router-dom'; +import { useNavigateCompat } from '../hooks/useNavigateCompat'; +import { useParamsCompat } from '../hooks/useParamsCompat'; import { ActionGroup, Alert, @@ -29,9 +30,8 @@ import './SecretForm/SecretForm.css'; export default function ManageSecretsPage() { const { t } = useTranslation('plugin__patterns-operator-console-plugin'); - const history = useHistory(); - const match = useRouteMatch<{ name: string }>('/patterns/secrets/:name'); - const name = match?.params?.name; + const navigate = useNavigateCompat(); + const { name } = useParamsCompat('/patterns/secrets/:name'); const [loading, setLoading] = React.useState(true); const [fetchError, setFetchError] = React.useState(null); @@ -171,7 +171,7 @@ export default function ManageSecretsPage() {

{t('This pattern does not have a secret template defined.')}

-
@@ -234,7 +234,7 @@ export default function ManageSecretsPage() { - diff --git a/console/src/components/PatternCatalogPage.tsx b/console/src/components/PatternCatalogPage.tsx index b79c05a8d..5dfbd7b65 100644 --- a/console/src/components/PatternCatalogPage.tsx +++ b/console/src/components/PatternCatalogPage.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; +import { useNavigateCompat } from '../hooks/useNavigateCompat'; import { Alert, @@ -166,7 +166,7 @@ const TIER_DESCRIPTIONS: Record = { export default function PatternCatalogPage() { const { t } = useTranslation('plugin__patterns-operator-console-plugin'); - const history = useHistory(); + const navigate = useNavigateCompat(); const [patterns, setPatterns] = React.useState([]); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); @@ -488,7 +488,7 @@ export default function PatternCatalogPage() { @@ -515,7 +515,7 @@ export default function PatternCatalogPage() { variant="primary" isDisabled={isDisabled} onClick={() => - history.push( + navigate( `/patterns/install/${pattern.catalogKey || pattern.name}`, ) } diff --git a/console/src/components/UninstallPatternPage.tsx b/console/src/components/UninstallPatternPage.tsx index 4353041cd..d5028b807 100644 --- a/console/src/components/UninstallPatternPage.tsx +++ b/console/src/components/UninstallPatternPage.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import { useHistory, useRouteMatch } from 'react-router-dom'; +import { useNavigateCompat } from '../hooks/useNavigateCompat'; +import { useParamsCompat } from '../hooks/useParamsCompat'; import { Alert, Button, @@ -29,9 +30,8 @@ const DELETION_PHASES: Record = { export default function UninstallPatternPage() { const { t } = useTranslation('plugin__patterns-operator-console-plugin'); - const history = useHistory(); - const match = useRouteMatch<{ name: string }>('/patterns/uninstall/:name'); - const name = match?.params?.name; + const navigate = useNavigateCompat(); + const { name } = useParamsCompat('/patterns/uninstall/:name'); const [status, setStatus] = React.useState(null); const [deleting, setDeleting] = React.useState(false); @@ -127,7 +127,7 @@ export default function UninstallPatternPage() { {deleted && (

{t('The pattern and all its associated resources have been fully deleted.')}

-
@@ -187,7 +187,7 @@ export default function UninstallPatternPage() { > {t('Confirm Uninstall')} - diff --git a/console/src/hooks/useNavigateCompat.ts b/console/src/hooks/useNavigateCompat.ts new file mode 100644 index 000000000..4b26bdc34 --- /dev/null +++ b/console/src/hooks/useNavigateCompat.ts @@ -0,0 +1,16 @@ +import * as React from 'react'; +import * as ReactRouterDom from 'react-router-dom'; + +const hasUseNavigate = typeof (ReactRouterDom as any).useNavigate === 'function'; + +// React Router v6 removed useHistory in favor of useNavigate. +// OCP 4.22+ ships v6; OCP 4.21 and earlier ship v5. +export const useNavigateCompat: () => (path: string) => void = hasUseNavigate + ? () => { + const navigate = (ReactRouterDom as any).useNavigate(); + return React.useCallback((path: string) => navigate(path), [navigate]); + } + : () => { + const history = (ReactRouterDom as any).useHistory(); + return React.useCallback((path: string) => history.push(path), [history]); + }; diff --git a/console/src/hooks/useParamsCompat.ts b/console/src/hooks/useParamsCompat.ts new file mode 100644 index 000000000..87d624fe9 --- /dev/null +++ b/console/src/hooks/useParamsCompat.ts @@ -0,0 +1,14 @@ +import * as ReactRouterDom from 'react-router-dom'; + +const isV6 = typeof (ReactRouterDom as any).useNavigate === 'function'; + +// React Router v5 (OCP < 4.22): useParams may not work if the console +// framework doesn't expose route params through the standard context. +// useRouteMatch explicitly matches the current URL against the given pattern. +// React Router v6 (OCP 4.22+): useParams is the standard API. +export const useParamsCompat: (pattern: string) => Record = isV6 + ? () => (ReactRouterDom as any).useParams() + : (pattern: string) => { + const match = (ReactRouterDom as any).useRouteMatch(pattern); + return match?.params || {}; + };