Fixed provider ordering to prevent bugs loading modals on refresh (#19089)
refs https://github.com/TryGhost/Product/issues/4174
This commit is contained in:
parent
8038c5854e
commit
32dacd9ff7
@ -1,6 +1,7 @@
|
||||
import MainContent from './MainContent';
|
||||
import {DesignSystemApp, DesignSystemAppProps} from '@tryghost/admin-x-design-system';
|
||||
import {FrameworkProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework';
|
||||
import {RoutingProvider} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
interface AppProps {
|
||||
framework: TopLevelFrameworkProps;
|
||||
@ -16,10 +17,12 @@ const modals = {
|
||||
|
||||
const App: React.FC<AppProps> = ({framework, designSystem}) => {
|
||||
return (
|
||||
<FrameworkProvider basePath='demo-x' modals={modals} {...framework}>
|
||||
<DesignSystemApp className='admin-x-demo' {...designSystem}>
|
||||
<MainContent />
|
||||
</DesignSystemApp>
|
||||
<FrameworkProvider {...framework}>
|
||||
<RoutingProvider basePath='demo-x' modals={modals}>
|
||||
<DesignSystemApp className='admin-x-demo' {...designSystem}>
|
||||
<MainContent />
|
||||
</DesignSystemApp>
|
||||
</RoutingProvider>
|
||||
</FrameworkProvider>
|
||||
);
|
||||
};
|
||||
|
@ -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,13 +22,13 @@ export interface FrameworkProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// children, basePath and modals should be provided by each app, while others are passed in from Ember
|
||||
export type TopLevelFrameworkProps = Omit<FrameworkProviderProps, 'children' | 'basePath' | 'modals'>;
|
||||
export type TopLevelFrameworkProps = Omit<FrameworkProviderProps, 'children'>;
|
||||
|
||||
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': '',
|
||||
@ -44,14 +42,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>
|
||||
|
@ -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());
|
||||
|
@ -1,3 +1,3 @@
|
||||
export {useRouteChangeCallback, useRouting} from './providers/RoutingProvider';
|
||||
export {default as RoutingProvider, useRouteChangeCallback, useRouting} from './providers/RoutingProvider';
|
||||
export type {ExternalLink, InternalLink, ModalComponent, RoutingModalProps} from './providers/RoutingProvider';
|
||||
|
||||
|
@ -3,6 +3,7 @@ import SettingsAppProvider, {OfficialTheme, UpgradeStatusType} from './component
|
||||
import SettingsRouter, {loadModals, modalPaths} from './components/providers/SettingsRouter';
|
||||
import {DesignSystemApp, DesignSystemAppProps} from '@tryghost/admin-x-design-system';
|
||||
import {FrameworkProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework';
|
||||
import {RoutingProvider} from '@tryghost/admin-x-framework/routing';
|
||||
import {ZapierTemplate} from './components/settings/advanced/integrations/ZapierModal';
|
||||
|
||||
interface AppProps {
|
||||
@ -15,12 +16,14 @@ interface AppProps {
|
||||
|
||||
function App({framework, designSystem, officialThemes, zapierTemplates, upgradeStatus}: AppProps) {
|
||||
return (
|
||||
<FrameworkProvider basePath='settings' modals={{paths: modalPaths, load: loadModals}} {...framework}>
|
||||
<FrameworkProvider {...framework}>
|
||||
<SettingsAppProvider officialThemes={officialThemes} upgradeStatus={upgradeStatus} zapierTemplates={zapierTemplates}>
|
||||
<DesignSystemApp className='admin-x-settings' {...designSystem}>
|
||||
<SettingsRouter />
|
||||
<MainContent />
|
||||
</DesignSystemApp>
|
||||
<RoutingProvider basePath='settings' modals={{paths: modalPaths, load: loadModals}}>
|
||||
<DesignSystemApp className='admin-x-settings' {...designSystem}>
|
||||
<SettingsRouter />
|
||||
<MainContent />
|
||||
</DesignSystemApp>
|
||||
</RoutingProvider>
|
||||
</SettingsAppProvider>
|
||||
</FrameworkProvider>
|
||||
);
|
||||
|
23
apps/admin-x-settings/test/acceptance/routing.test.ts
Normal file
23
apps/admin-x-settings/test/acceptance/routing.test.ts
Normal 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$/);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user