Fixed provider ordering to prevent bugs loading modals on refresh (#19089)

refs https://github.com/TryGhost/Product/issues/4174
This commit is contained in:
Jono M 2023-11-22 07:51:10 +00:00 committed by Jono Mingard
parent 34dc2f8c1e
commit db7d44a76a
5 changed files with 41 additions and 17 deletions

View File

@ -2,13 +2,11 @@ import {ErrorBoundary as SentryErrorBoundary} from '@sentry/react';
import {QueryClientProvider} from '@tanstack/react-query';
import {ReactNode, createContext, useContext} from 'react';
import queryClient from '../utils/queryClient';
import RoutingProvider, {RoutingProviderProps} from './RoutingProvider';
import {ExternalLink} from './RoutingProvider';
export interface FrameworkProviderProps {
basePath: string;
ghostVersion: string;
externalNavigate: RoutingProviderProps['externalNavigate'];
modals: RoutingProviderProps['modals'];
externalNavigate: (link: ExternalLink) => void;
unsplashConfig: {
Authorization: string;
'Accept-Version': string;
@ -24,10 +22,11 @@ export interface FrameworkProviderProps {
children: ReactNode;
}
export type FrameworkContextType = Omit<FrameworkProviderProps, 'basePath' | 'externalNavigate' | 'modals' | 'children'>;
export type FrameworkContextType = Omit<FrameworkProviderProps, 'children'>;
const FrameworkContext = createContext<FrameworkContextType>({
ghostVersion: '',
externalNavigate: () => {},
unsplashConfig: {
Authorization: '',
'Accept-Version': '',
@ -41,14 +40,12 @@ const FrameworkContext = createContext<FrameworkContextType>({
onDelete: () => {}
});
function FrameworkProvider({externalNavigate, basePath, modals, children, ...props}: FrameworkProviderProps) {
function FrameworkProvider({children, ...props}: FrameworkProviderProps) {
return (
<SentryErrorBoundary>
<QueryClientProvider client={queryClient}>
<FrameworkContext.Provider value={props}>
<RoutingProvider basePath={basePath} externalNavigate={externalNavigate} modals={modals}>
{children}
</RoutingProvider>
{children}
</FrameworkContext.Provider>
</QueryClientProvider>
</SentryErrorBoundary>

View File

@ -1,5 +1,6 @@
import NiceModal, {NiceModalHocProps} from '@ebay/nice-modal-react';
import React, {createContext, useCallback, useContext, useEffect, useState} from 'react';
import {useFramework} from './FrameworkProvider';
export type RouteParams = Record<string, string>
@ -92,12 +93,12 @@ const matchRoute = (pathname: string, routeDefinition: string) => {
export interface RoutingProviderProps {
basePath: string;
externalNavigate: (link: ExternalLink) => void;
modals?: {paths: Record<string, string>, load: () => Promise<ModalsModule>}
children: React.ReactNode;
}
const RoutingProvider: React.FC<RoutingProviderProps> = ({basePath, externalNavigate, modals, children}) => {
const RoutingProvider: React.FC<RoutingProviderProps> = ({basePath, modals, children}) => {
const {externalNavigate} = useFramework();
const [route, setRoute] = useState<string | undefined>(undefined);
const [loadingModal, setLoadingModal] = useState(false);
const [eventTarget] = useState(new EventTarget());

View File

@ -1,3 +1,3 @@
export {useRouteChangeCallback, useRouting} from './providers/RoutingProvider';
export {default as RoutingProvider, useRouteChangeCallback, useRouting} from './providers/RoutingProvider';
export type {ExternalLink, InternalLink, RoutingModalProps} from './providers/RoutingProvider';

View File

@ -3,6 +3,7 @@ import SettingsAppProvider, {OfficialTheme, UpgradeStatusType} from './component
import SettingsRouter, {loadModals, modalPaths} from './components/providers/SettingsRouter';
import {DesignSystemApp, FetchKoenigLexical} from '@tryghost/admin-x-design-system';
import {FrameworkProvider, FrameworkProviderProps} from '@tryghost/admin-x-framework';
import {RoutingProvider} from '@tryghost/admin-x-framework/routing';
import {ZapierTemplate} from './components/settings/advanced/integrations/ZapierModal';
interface AppProps extends Omit<FrameworkProviderProps, 'basePath' | 'modals' | 'children'> {
@ -15,12 +16,14 @@ interface AppProps extends Omit<FrameworkProviderProps, 'basePath' | 'modals' |
function App({officialThemes, zapierTemplates, upgradeStatus, darkMode, fetchKoenigLexical, ...props}: AppProps) {
return (
<FrameworkProvider basePath='settings' modals={{paths: modalPaths, load: loadModals}} {...props}>
<FrameworkProvider {...props}>
<SettingsAppProvider officialThemes={officialThemes} upgradeStatus={upgradeStatus} zapierTemplates={zapierTemplates}>
<DesignSystemApp className='admin-x-settings' darkMode={darkMode} fetchKoenigLexical={fetchKoenigLexical} id='admin-x-settings'>
<SettingsRouter />
<MainContent />
</DesignSystemApp>
<RoutingProvider basePath='settings' modals={{paths: modalPaths, load: loadModals}}>
<DesignSystemApp className='admin-x-settings' darkMode={darkMode} fetchKoenigLexical={fetchKoenigLexical} id='admin-x-settings'>
<SettingsRouter />
<MainContent />
</DesignSystemApp>
</RoutingProvider>
</SettingsAppProvider>
</FrameworkProvider>
);

View File

@ -0,0 +1,23 @@
import {expect, test} from '@playwright/test';
import {globalDataRequests, mockApi} from '../utils/acceptance';
test.describe('Routing', async () => {
test('Reopens the opened modal when refreshing the page', async ({page}) => {
await mockApi({page, requests: globalDataRequests});
await page.goto('/');
const section = page.getByTestId('portal');
await section.getByRole('button', {name: 'Customize'}).click();
await page.waitForSelector('[data-testid="portal-modal"]');
expect(page.url()).toMatch(/\/portal\/edit$/);
await page.reload();
await page.waitForSelector('[data-testid="portal-modal"]');
expect(page.url()).toMatch(/\/portal\/edit$/);
});
});