mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-04-11 00:56:20 +00:00
Do not inject remoteServers state or actions
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { changeThemeInMarkup, getSystemPreferredTheme } from '@shlinkio/shlink-frontend-kit';
|
import { changeThemeInMarkup, getSystemPreferredTheme } from '@shlinkio/shlink-frontend-kit';
|
||||||
|
import type { HttpClient } from '@shlinkio/shlink-js-sdk';
|
||||||
import type { Settings as AppSettings } from '@shlinkio/shlink-web-component/settings';
|
import type { Settings as AppSettings } from '@shlinkio/shlink-web-component/settings';
|
||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Route, Routes, useLocation } from 'react-router';
|
import { Route, Routes, useLocation } from 'react-router';
|
||||||
import { AppUpdateBanner } from '../common/AppUpdateBanner';
|
import { AppUpdateBanner } from '../common/AppUpdateBanner';
|
||||||
import { MainHeader } from '../common/MainHeader';
|
import { MainHeader } from '../common/MainHeader';
|
||||||
@@ -10,14 +11,12 @@ import { NotFound } from '../common/NotFound';
|
|||||||
import { ShlinkVersionsContainer } from '../common/ShlinkVersionsContainer';
|
import { ShlinkVersionsContainer } from '../common/ShlinkVersionsContainer';
|
||||||
import type { FCWithDeps } from '../container/utils';
|
import type { FCWithDeps } from '../container/utils';
|
||||||
import { componentFactory, useDependencies } from '../container/utils';
|
import { componentFactory, useDependencies } from '../container/utils';
|
||||||
import type { ServersMap } from '../servers/data';
|
|
||||||
import { EditServer } from '../servers/EditServer';
|
import { EditServer } from '../servers/EditServer';
|
||||||
|
import { useLoadRemoteServers } from '../servers/reducers/remoteServers';
|
||||||
import { Settings } from '../settings/Settings';
|
import { Settings } from '../settings/Settings';
|
||||||
import { forceUpdate } from '../utils/helpers/sw';
|
import { forceUpdate } from '../utils/helpers/sw';
|
||||||
|
|
||||||
type AppProps = {
|
export type AppProps = {
|
||||||
fetchServers: () => void;
|
|
||||||
servers: ServersMap;
|
|
||||||
settings: AppSettings;
|
settings: AppSettings;
|
||||||
resetAppUpdate: () => void;
|
resetAppUpdate: () => void;
|
||||||
appUpdated: boolean;
|
appUpdated: boolean;
|
||||||
@@ -28,29 +27,22 @@ type AppDeps = {
|
|||||||
ShlinkWebComponentContainer: FC;
|
ShlinkWebComponentContainer: FC;
|
||||||
CreateServer: FC;
|
CreateServer: FC;
|
||||||
ManageServers: FC;
|
ManageServers: FC;
|
||||||
|
HttpClient: HttpClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
const App: FCWithDeps<AppProps, AppDeps> = (
|
const App: FCWithDeps<AppProps, AppDeps> = ({ settings, appUpdated, resetAppUpdate }) => {
|
||||||
{ fetchServers, servers, settings, appUpdated, resetAppUpdate },
|
|
||||||
) => {
|
|
||||||
const {
|
const {
|
||||||
Home,
|
Home,
|
||||||
ShlinkWebComponentContainer,
|
ShlinkWebComponentContainer,
|
||||||
CreateServer,
|
CreateServer,
|
||||||
ManageServers,
|
ManageServers,
|
||||||
|
HttpClient: httpClient,
|
||||||
} = useDependencies(App);
|
} = useDependencies(App);
|
||||||
|
|
||||||
const location = useLocation();
|
useLoadRemoteServers(httpClient);
|
||||||
const initialServers = useRef(servers);
|
|
||||||
const isHome = location.pathname === '/';
|
|
||||||
|
|
||||||
useEffect(() => {
|
const location = useLocation();
|
||||||
// Try to fetch the remote servers if the list is empty during first render.
|
const isHome = location.pathname === '/';
|
||||||
// We use a ref because we don't care if the servers list becomes empty later.
|
|
||||||
if (Object.keys(initialServers.current).length === 0) {
|
|
||||||
fetchServers();
|
|
||||||
}
|
|
||||||
}, [fetchServers]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changeThemeInMarkup(settings.ui?.theme ?? getSystemPreferredTheme());
|
changeThemeInMarkup(settings.ui?.theme ?? getSystemPreferredTheme());
|
||||||
@@ -100,4 +92,5 @@ export const AppFactory = componentFactory(App, [
|
|||||||
'ShlinkWebComponentContainer',
|
'ShlinkWebComponentContainer',
|
||||||
'CreateServer',
|
'CreateServer',
|
||||||
'ManageServers',
|
'ManageServers',
|
||||||
|
'HttpClient',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates';
|
|||||||
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
// Components
|
// Components
|
||||||
bottle.factory('App', AppFactory);
|
bottle.factory('App', AppFactory);
|
||||||
bottle.decorator('App', connect(['servers', 'settings', 'appUpdated'], ['fetchServers', 'resetAppUpdate']));
|
bottle.decorator('App', connect(['settings', 'appUpdated'], ['resetAppUpdate']));
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable);
|
bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable);
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
import type { HttpClient } from '@shlinkio/shlink-js-sdk';
|
import type { HttpClient } from '@shlinkio/shlink-js-sdk';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import pack from '../../../package.json';
|
import pack from '../../../package.json';
|
||||||
|
import { useAppDispatch } from '../../store';
|
||||||
import { createAsyncThunk } from '../../store/helpers';
|
import { createAsyncThunk } from '../../store/helpers';
|
||||||
import { hasServerData } from '../data';
|
import { hasServerData } from '../data';
|
||||||
import { ensureUniqueIds } from '../helpers';
|
import { ensureUniqueIds } from '../helpers';
|
||||||
import { createServers } from './servers';
|
import { createServers, useServers } from './servers';
|
||||||
|
|
||||||
const responseToServersList = (data: any) => ensureUniqueIds(
|
const responseToServersList = (data: any) => ensureUniqueIds(
|
||||||
{},
|
{},
|
||||||
(Array.isArray(data) ? data.filter(hasServerData) : []),
|
(Array.isArray(data) ? data.filter(hasServerData) : []),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const fetchServers = (httpClient: HttpClient) => createAsyncThunk(
|
export const fetchServers = createAsyncThunk(
|
||||||
'shlink/remoteServers/fetchServers',
|
'shlink/remoteServers/fetchServers',
|
||||||
async (_: void, { dispatch }): Promise<void> => {
|
async (httpClient: HttpClient, { dispatch }): Promise<void> => {
|
||||||
const resp = await httpClient.jsonRequest<any>(`${pack.homepage}/servers.json`);
|
const resp = await httpClient.jsonRequest<any>(`${pack.homepage}/servers.json`);
|
||||||
const result = responseToServersList(resp);
|
const result = responseToServersList(resp);
|
||||||
|
|
||||||
dispatch(createServers(result));
|
dispatch(createServers(result));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const useRemoteServers = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const dispatchFetchServer = useCallback((httpClient: HttpClient) => dispatch(fetchServers(httpClient)), [dispatch]);
|
||||||
|
|
||||||
|
return { fetchServers: dispatchFetchServer };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLoadRemoteServers = (httpClient: HttpClient) => {
|
||||||
|
const { fetchServers } = useRemoteServers();
|
||||||
|
const { servers } = useServers();
|
||||||
|
const initialServers = useRef(servers);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Try to fetch the remote servers if the list is empty during first render.
|
||||||
|
// We use a ref because we don't care if the servers list becomes empty later.
|
||||||
|
if (Object.keys(initialServers.current).length === 0) {
|
||||||
|
fetchServers(httpClient);
|
||||||
|
}
|
||||||
|
}, [fetchServers, httpClient]);
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { CreateServerFactory } from '../CreateServer';
|
|||||||
import { ImportServersBtnFactory } from '../helpers/ImportServersBtn';
|
import { ImportServersBtnFactory } from '../helpers/ImportServersBtn';
|
||||||
import { withoutSelectedServer } from '../helpers/withoutSelectedServer';
|
import { withoutSelectedServer } from '../helpers/withoutSelectedServer';
|
||||||
import { ManageServersFactory } from '../ManageServers';
|
import { ManageServersFactory } from '../ManageServers';
|
||||||
import { fetchServers } from '../reducers/remoteServers';
|
|
||||||
import { ServersExporter } from './ServersExporter';
|
import { ServersExporter } from './ServersExporter';
|
||||||
import { ServersImporter } from './ServersImporter';
|
import { ServersImporter } from './ServersImporter';
|
||||||
|
|
||||||
@@ -20,7 +19,4 @@ export const provideServices = (bottle: Bottle) => {
|
|||||||
// Services
|
// Services
|
||||||
bottle.service('ServersImporter', ServersImporter, 'csvToJson');
|
bottle.service('ServersImporter', ServersImporter, 'csvToJson');
|
||||||
bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'jsonToCsv');
|
bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'jsonToCsv');
|
||||||
|
|
||||||
// Actions
|
|
||||||
bottle.serviceFactory('fetchServers', fetchServers, 'HttpClient');
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { HttpClient } from '@shlinkio/shlink-js-sdk';
|
||||||
import { act, screen } from '@testing-library/react';
|
import { act, screen } from '@testing-library/react';
|
||||||
import { fromPartial } from '@total-typescript/shoehorn';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router';
|
import { MemoryRouter } from 'react-router';
|
||||||
@@ -13,17 +14,12 @@ describe('<App />', () => {
|
|||||||
ShlinkWebComponentContainer: () => <>ShlinkWebComponentContainer</>,
|
ShlinkWebComponentContainer: () => <>ShlinkWebComponentContainer</>,
|
||||||
CreateServer: () => <>CreateServer</>,
|
CreateServer: () => <>CreateServer</>,
|
||||||
ManageServers: () => <>ManageServers</>,
|
ManageServers: () => <>ManageServers</>,
|
||||||
|
HttpClient: fromPartial<HttpClient>({}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const setUp = async (activeRoute = '/') => act(() => renderWithStore(
|
const setUp = async (activeRoute = '/') => act(() => renderWithStore(
|
||||||
<MemoryRouter initialEntries={[{ pathname: activeRoute }]}>
|
<MemoryRouter initialEntries={[{ pathname: activeRoute }]}>
|
||||||
<App
|
<App settings={fromPartial({})} appUpdated={false} resetAppUpdate={() => {}} />
|
||||||
fetchServers={() => {}}
|
|
||||||
servers={{}}
|
|
||||||
settings={fromPartial({})}
|
|
||||||
appUpdated={false}
|
|
||||||
resetAppUpdate={() => {}}
|
|
||||||
/>
|
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
{
|
{
|
||||||
initialState: {
|
initialState: {
|
||||||
|
|||||||
@@ -79,9 +79,8 @@ describe('remoteServersReducer', () => {
|
|||||||
},
|
},
|
||||||
])('tries to fetch servers from remote', async ({ serversArray, expectedNewServers }) => {
|
])('tries to fetch servers from remote', async ({ serversArray, expectedNewServers }) => {
|
||||||
jsonRequest.mockResolvedValue(serversArray);
|
jsonRequest.mockResolvedValue(serversArray);
|
||||||
const doFetchServers = fetchServers(httpClient);
|
|
||||||
|
|
||||||
await doFetchServers()(dispatch, vi.fn(), {});
|
await fetchServers(httpClient)(dispatch, vi.fn(), {});
|
||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ payload: expectedNewServers }));
|
expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ payload: expectedNewServers }));
|
||||||
|
|||||||
Reference in New Issue
Block a user