mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-12-11 10:13:55 -06:00
Provide API client to shlink-web-component
This commit is contained in:
parent
b3122219be
commit
2ac5236cc7
@ -1,5 +1,6 @@
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder';
|
||||
import { isReachableServer } from '../servers/data';
|
||||
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
||||
import { ShlinkWebComponent } from '../shlink-web-component';
|
||||
@ -13,6 +14,7 @@ interface MenuLayoutProps {
|
||||
}
|
||||
|
||||
export const MenuLayout = (
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||
ServerError: FC,
|
||||
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotPresent, sidebarPresent, settings }) => {
|
||||
const showContent = isReachableServer(selectedServer);
|
||||
@ -29,6 +31,7 @@ export const MenuLayout = (
|
||||
return (
|
||||
<ShlinkWebComponent
|
||||
serverVersion={selectedServer.version}
|
||||
apiClient={buildShlinkApiClient(selectedServer)}
|
||||
settings={settings}
|
||||
routesPrefix={`/server/${selectedServer.id}`}
|
||||
/>
|
||||
|
||||
@ -31,7 +31,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.decorator('Home', withoutSelectedServer);
|
||||
bottle.decorator('Home', connect(['servers'], ['resetSelectedServer']));
|
||||
|
||||
bottle.serviceFactory('MenuLayout', MenuLayout, 'ServerError');
|
||||
bottle.serviceFactory('MenuLayout', MenuLayout, 'buildShlinkApiClient', 'ServerError');
|
||||
bottle.decorator('MenuLayout', connect(
|
||||
['selectedServer', 'settings'],
|
||||
['selectServer', 'sidebarPresent', 'sidebarNotPresent'],
|
||||
|
||||
75
src/shlink-web-component/Main.tsx
Normal file
75
src/shlink-web-component/Main.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { AsideMenu } from '../common/AsideMenu';
|
||||
import { NotFound } from '../common/NotFound';
|
||||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||
import { useFeature } from './utils/features';
|
||||
|
||||
type MainProps = {
|
||||
routesPrefix?: string;
|
||||
};
|
||||
|
||||
export const Main = (
|
||||
TagsList: FC,
|
||||
ShortUrlsList: FC,
|
||||
CreateShortUrl: FC,
|
||||
ShortUrlVisits: FC,
|
||||
TagVisits: FC,
|
||||
DomainVisits: FC,
|
||||
OrphanVisits: FC,
|
||||
NonOrphanVisits: FC,
|
||||
Overview: FC,
|
||||
EditShortUrl: FC,
|
||||
ManageDomains: FC,
|
||||
): FC<MainProps> => ({ routesPrefix = '' }) => {
|
||||
const location = useLocation();
|
||||
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
|
||||
useEffect(() => hideSidebar(), [location]);
|
||||
|
||||
const addNonOrphanVisitsRoute = useFeature('nonOrphanVisits');
|
||||
const addDomainVisitsRoute = useFeature('domainVisits');
|
||||
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
|
||||
const swipeableProps = useSwipeable(showSidebar, hideSidebar);
|
||||
|
||||
// TODO Check if this is already wrapped by a router, and wrap otherwise
|
||||
|
||||
return (
|
||||
<>
|
||||
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
|
||||
|
||||
<div {...swipeableProps} className="menu-layout__swipeable">
|
||||
<div className="menu-layout__swipeable-inner">
|
||||
<AsideMenu routePrefix={routesPrefix} showOnMobile={sidebarVisible} />
|
||||
<div className="menu-layout__container" onClick={() => hideSidebar()}>
|
||||
<div className="container-xl">
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to="overview" />} />
|
||||
<Route path="/overview" element={<Overview />} />
|
||||
<Route path="/list-short-urls/:page" element={<ShortUrlsList />} />
|
||||
<Route path="/create-short-url" element={<CreateShortUrl />} />
|
||||
<Route path="/short-code/:shortCode/visits/*" element={<ShortUrlVisits />} />
|
||||
<Route path="/short-code/:shortCode/edit" element={<EditShortUrl />} />
|
||||
<Route path="/tag/:tag/visits/*" element={<TagVisits />} />
|
||||
{addDomainVisitsRoute && <Route path="/domain/:domain/visits/*" element={<DomainVisits />} />}
|
||||
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
||||
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
||||
<Route path="/manage-tags" element={<TagsList />} />
|
||||
<Route path="/manage-domains" element={<ManageDomains />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<NotFound to={`${routesPrefix}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export type MainType = ReturnType<typeof Main>;
|
||||
@ -1,88 +1,46 @@
|
||||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Store } from '@reduxjs/toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import type Bottle from 'bottlejs';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { AsideMenu } from '../common/AsideMenu';
|
||||
import { NotFound } from '../common/NotFound';
|
||||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||
import type { SemVer } from '../utils/helpers/version';
|
||||
import { setUpStore } from './container/store';
|
||||
import { FeaturesProvider, useFeatures } from './utils/features';
|
||||
import type { Settings } from './utils/settings';
|
||||
import { SettingsProvider } from './utils/settings';
|
||||
|
||||
type ShlinkWebComponentProps = {
|
||||
routesPrefix?: string;
|
||||
serverVersion: SemVer;
|
||||
settings?: Settings;
|
||||
serverVersion: SemVer;
|
||||
apiClient: any;
|
||||
};
|
||||
|
||||
export const ShlinkWebComponent = (
|
||||
TagsList: FC,
|
||||
ShortUrlsList: FC,
|
||||
CreateShortUrl: FC,
|
||||
ShortUrlVisits: FC,
|
||||
TagVisits: FC,
|
||||
DomainVisits: FC,
|
||||
OrphanVisits: FC,
|
||||
NonOrphanVisits: FC,
|
||||
Overview: FC,
|
||||
EditShortUrl: FC,
|
||||
ManageDomains: FC,
|
||||
store: Store,
|
||||
): FC<ShlinkWebComponentProps> => ({ routesPrefix = '', serverVersion, settings }) => {
|
||||
const location = useLocation();
|
||||
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
|
||||
useEffect(() => hideSidebar(), [location]);
|
||||
|
||||
export const createShlinkWebComponent = (
|
||||
bottle: Bottle,
|
||||
): FC<ShlinkWebComponentProps> => ({ routesPrefix = '', serverVersion, settings, apiClient }) => {
|
||||
const features = useFeatures(serverVersion);
|
||||
const addNonOrphanVisitsRoute = features.nonOrphanVisits;
|
||||
const addDomainVisitsRoute = features.domainVisits;
|
||||
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
|
||||
const swipeableProps = useSwipeable(showSidebar, hideSidebar);
|
||||
const mainContent = useRef<ReactNode>();
|
||||
const [theStore, setStore] = useState<Store | undefined>();
|
||||
|
||||
// TODO Check if this is already wrapped by a router, and wrap otherwise
|
||||
useEffect(() => {
|
||||
bottle.constant('apiClient', apiClient);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
// It's important to not try to resolve services before the API client has been registered, as many other services
|
||||
// depend on it
|
||||
const { container } = bottle;
|
||||
const { Main } = container;
|
||||
mainContent.current = <Main routesPrefix={routesPrefix} />;
|
||||
setStore(setUpStore(container));
|
||||
}, []);
|
||||
|
||||
return !theStore ? <></> : (
|
||||
<Provider store={theStore}>
|
||||
<SettingsProvider value={settings}>
|
||||
<FeaturesProvider value={features}>
|
||||
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
|
||||
|
||||
<div {...swipeableProps} className="menu-layout__swipeable">
|
||||
<div className="menu-layout__swipeable-inner">
|
||||
<AsideMenu routePrefix={routesPrefix} showOnMobile={sidebarVisible} />
|
||||
<div className="menu-layout__container" onClick={() => hideSidebar()}>
|
||||
<div className="container-xl">
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to="overview" />} />
|
||||
<Route path="/overview" element={<Overview />} />
|
||||
<Route path="/list-short-urls/:page" element={<ShortUrlsList />} />
|
||||
<Route path="/create-short-url" element={<CreateShortUrl />} />
|
||||
<Route path="/short-code/:shortCode/visits/*" element={<ShortUrlVisits />} />
|
||||
<Route path="/short-code/:shortCode/edit" element={<EditShortUrl />} />
|
||||
<Route path="/tag/:tag/visits/*" element={<TagVisits />} />
|
||||
{addDomainVisitsRoute && <Route path="/domain/:domain/visits/*" element={<DomainVisits />} />}
|
||||
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
||||
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
||||
<Route path="/manage-tags" element={<TagsList />} />
|
||||
<Route path="/manage-domains" element={<ManageDomains />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<NotFound to={`${routesPrefix}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{mainContent.current}
|
||||
</FeaturesProvider>
|
||||
</SettingsProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export type ShlinkWebComponentType = ReturnType<typeof ShlinkWebComponent>;
|
||||
|
||||
@ -16,13 +16,12 @@ import { provideServices as provideShortUrlsServices } from '../short-urls/servi
|
||||
import { provideServices as provideTagsServices } from '../tags/services/provideServices';
|
||||
import { provideServices as provideVisitsServices } from '../visits/services/provideServices';
|
||||
import { provideServices as provideWebComponentServices } from './provideServices';
|
||||
import { setUpStore } from './store';
|
||||
|
||||
type LazyActionMap = Record<string, Function>;
|
||||
|
||||
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;
|
||||
|
||||
const bottle = new Bottle();
|
||||
export const bottle = new Bottle();
|
||||
|
||||
export const { container } = bottle;
|
||||
|
||||
@ -66,6 +65,3 @@ bottle.constant('jsonToCsv', jsonToCsv);
|
||||
bottle.constant('setTimeout', window.setTimeout);
|
||||
bottle.constant('clearTimeout', window.clearTimeout);
|
||||
bottle.serviceFactory('useTimeoutToggle', useTimeoutToggle, 'setTimeout', 'clearTimeout');
|
||||
|
||||
// FIXME This has to be last. Find a way to delay the creation, perhaps using some kind of runtime factory
|
||||
bottle.constant('store', setUpStore(container));
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type Bottle from 'bottlejs';
|
||||
import { ShlinkWebComponent } from '../ShlinkWebComponent';
|
||||
import { Main } from '../Main';
|
||||
|
||||
export const provideServices = (bottle: Bottle) => {
|
||||
bottle.serviceFactory(
|
||||
'ShlinkWebComponent',
|
||||
ShlinkWebComponent,
|
||||
'Main',
|
||||
Main,
|
||||
'TagsList',
|
||||
'ShortUrlsList',
|
||||
'CreateShortUrl',
|
||||
@ -16,6 +16,5 @@ export const provideServices = (bottle: Bottle) => {
|
||||
'Overview',
|
||||
'EditShortUrl',
|
||||
'ManageDomains',
|
||||
'store',
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkDomainRedirects } from '../../../api/types';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
|
||||
@ -10,13 +10,11 @@ export interface EditDomainRedirects {
|
||||
}
|
||||
|
||||
export const editDomainRedirects = (
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||
apiClient: ShlinkApiClient,
|
||||
) => createAsyncThunk(
|
||||
EDIT_DOMAIN_REDIRECTS,
|
||||
async ({ domain, redirects: providedRedirects }: EditDomainRedirects, { getState }): Promise<EditDomainRedirects> => {
|
||||
const { editDomainRedirects: shlinkEditDomainRedirects } = buildShlinkApiClient(getState);
|
||||
const redirects = await shlinkEditDomainRedirects({ domain, ...providedRedirects });
|
||||
|
||||
async ({ domain, redirects: providedRedirects }: EditDomainRedirects): Promise<EditDomainRedirects> => {
|
||||
const redirects = await apiClient.editDomainRedirects({ domain, ...providedRedirects });
|
||||
return { domain, redirects };
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import type { AsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkDomainRedirects } from '../../../api/types';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { hasServerData } from '../../../servers/data';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
import { replaceAuthorityFromUri } from '../../../utils/helpers/uri';
|
||||
import type { Domain, DomainStatus } from '../data';
|
||||
import type { EditDomainRedirects } from './domainRedirects';
|
||||
|
||||
@ -45,12 +44,11 @@ export const replaceStatusOnDomain = (domain: string, status: DomainStatus) =>
|
||||
(d: Domain): Domain => (d.domain !== domain ? d : { ...d, status });
|
||||
|
||||
export const domainsListReducerCreator = (
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||
apiClient: ShlinkApiClient,
|
||||
editDomainRedirects: AsyncThunk<EditDomainRedirects, any, any>,
|
||||
) => {
|
||||
const listDomains = createAsyncThunk(`${REDUCER_PREFIX}/listDomains`, async (_: void, { getState }): Promise<ListDomains> => {
|
||||
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
||||
const { data, defaultRedirects } = await shlinkListDomains();
|
||||
const listDomains = createAsyncThunk(`${REDUCER_PREFIX}/listDomains`, async (): Promise<ListDomains> => {
|
||||
const { data, defaultRedirects } = await apiClient.listDomains();
|
||||
|
||||
return {
|
||||
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
||||
@ -68,13 +66,13 @@ export const domainsListReducerCreator = (
|
||||
}
|
||||
|
||||
try {
|
||||
const { url, ...rest } = selectedServer;
|
||||
const { health } = buildShlinkApiClient({
|
||||
...rest,
|
||||
url: replaceAuthorityFromUri(url, domain),
|
||||
});
|
||||
|
||||
const { status } = await health();
|
||||
// FIXME This should call different domains
|
||||
// const { url, ...rest } = selectedServer;
|
||||
// const { health } = buildShlinkApiClient({
|
||||
// ...rest,
|
||||
// url: replaceAuthorityFromUri(url, domain),
|
||||
// });
|
||||
const { status } = await apiClient.health();
|
||||
|
||||
return { domain, status: status === 'pass' ? 'valid' : 'invalid' };
|
||||
} catch (e) {
|
||||
|
||||
@ -21,7 +21,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory(
|
||||
'domainsListReducerCreator',
|
||||
domainsListReducerCreator,
|
||||
'buildShlinkApiClient',
|
||||
'apiClient',
|
||||
'editDomainRedirects',
|
||||
);
|
||||
bottle.serviceFactory('domainsListReducer', prop('reducer'), 'domainsListReducerCreator');
|
||||
@ -29,6 +29,6 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
// Actions
|
||||
bottle.serviceFactory('listDomains', prop('listDomains'), 'domainsListReducerCreator');
|
||||
bottle.serviceFactory('filterDomains', prop('filterDomains'), 'domainsListReducerCreator');
|
||||
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'apiClient');
|
||||
bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsListReducerCreator');
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { container } from './container';
|
||||
import type { ShlinkWebComponentType } from './ShlinkWebComponent';
|
||||
import { bottle } from './container';
|
||||
import { createShlinkWebComponent } from './ShlinkWebComponent';
|
||||
|
||||
export const ShlinkWebComponent = container.ShlinkWebComponent as ShlinkWebComponentType;
|
||||
export const ShlinkWebComponent = createShlinkWebComponent(bottle);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkMercureInfo } from '../../../api/types';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
|
||||
@ -16,17 +16,17 @@ const initialState: MercureInfo = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const mercureInfoReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||
export const mercureInfoReducerCreator = (apiClient: ShlinkApiClient) => {
|
||||
const loadMercureInfo = createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/loadMercureInfo`,
|
||||
(_: void, { getState }): Promise<ShlinkMercureInfo> =>
|
||||
// TODO Get settings here, where info is only available via hook
|
||||
(): Promise<ShlinkMercureInfo> =>
|
||||
// FIXME Get settings here somehow, as they are only available via hook
|
||||
// const { settings } = getState();
|
||||
// if (!settings.realTimeUpdates.enabled) {
|
||||
// throw new Error('Real time updates not enabled');
|
||||
// }
|
||||
|
||||
buildShlinkApiClient(getState).mercureInfo()
|
||||
apiClient.mercureInfo()
|
||||
,
|
||||
);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { mercureInfoReducerCreator } from '../reducers/mercureInfo';
|
||||
|
||||
export const provideServices = (bottle: Bottle) => {
|
||||
// Reducer
|
||||
bottle.serviceFactory('mercureInfoReducerCreator', mercureInfoReducerCreator, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('mercureInfoReducerCreator', mercureInfoReducerCreator, 'apiClient');
|
||||
bottle.serviceFactory('mercureInfoReducer', prop('reducer'), 'mercureInfoReducerCreator');
|
||||
|
||||
// Actions
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ReportExporter } from '../../../common/services/ReportExporter';
|
||||
import type { SelectedServer } from '../../../servers/data';
|
||||
import { isServerWithId } from '../../../servers/data';
|
||||
@ -20,7 +20,7 @@ interface ExportShortUrlsBtnConnectProps extends ExportShortUrlsBtnProps {
|
||||
const itemsPerPage = 20;
|
||||
|
||||
export const ExportShortUrlsBtn = (
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||
apiClient: ShlinkApiClient,
|
||||
{ exportShortUrls }: ReportExporter,
|
||||
): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => {
|
||||
const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery();
|
||||
@ -31,9 +31,8 @@ export const ExportShortUrlsBtn = (
|
||||
}
|
||||
|
||||
const totalPages = amount / itemsPerPage;
|
||||
const { listShortUrls } = buildShlinkApiClient(selectedServer);
|
||||
const loadAllUrls = async (page = 1): Promise<ShortUrl[]> => {
|
||||
const { data } = await listShortUrls(
|
||||
const { data } = await apiClient.listShortUrls(
|
||||
{ page: `${page}`, tags, searchTerm: search, startDate, endDate, orderBy, tagsMode, itemsPerPage },
|
||||
);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -36,9 +36,9 @@ const initialState: ShortUrlCreation = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const createShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
|
||||
export const createShortUrl = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/createShortUrl`,
|
||||
(data: ShortUrlData, { getState }): Promise<ShortUrl> => buildShlinkApiClient(getState).createShortUrl(data),
|
||||
(data: ShortUrlData): Promise<ShortUrl> => apiClient.createShortUrl(data),
|
||||
);
|
||||
|
||||
export const shortUrlCreationReducerCreator = (createShortUrlThunk: ReturnType<typeof createShortUrl>) => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -22,11 +22,10 @@ const initialState: ShortUrlDeletion = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const deleteShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
|
||||
export const deleteShortUrl = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/deleteShortUrl`,
|
||||
async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise<ShortUrlIdentifier> => {
|
||||
const { deleteShortUrl: shlinkDeleteShortUrl } = buildShlinkApiClient(getState);
|
||||
await shlinkDeleteShortUrl(shortCode, domain);
|
||||
async ({ shortCode, domain }: ShortUrlIdentifier): Promise<ShortUrlIdentifier> => {
|
||||
await apiClient.deleteShortUrl(shortCode, domain);
|
||||
return { shortCode, domain };
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -23,14 +23,14 @@ const initialState: ShortUrlDetail = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const shortUrlDetailReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||
export const shortUrlDetailReducerCreator = (apiClient: ShlinkApiClient) => {
|
||||
const getShortUrlDetail = createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/getShortUrlDetail`,
|
||||
async ({ shortCode, domain }: ShortUrlIdentifier, { getState }): Promise<ShortUrl> => {
|
||||
const { shortUrlsList } = getState();
|
||||
const alreadyLoaded = shortUrlsList?.shortUrls?.data.find((url) => shortUrlMatches(url, shortCode, domain));
|
||||
|
||||
return alreadyLoaded ?? await buildShlinkApiClient(getState).getShortUrl(shortCode, domain);
|
||||
return alreadyLoaded ?? await apiClient.getShortUrl(shortCode, domain);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -28,12 +28,11 @@ const initialState: ShortUrlEdition = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const editShortUrl = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
|
||||
export const editShortUrl = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/editShortUrl`,
|
||||
({ shortCode, domain, data }: EditShortUrl, { getState }): Promise<ShortUrl> => {
|
||||
const { updateShortUrl } = buildShlinkApiClient(getState);
|
||||
return updateShortUrl(shortCode, domain, data as any); // FIXME parse dates
|
||||
},
|
||||
({ shortCode, domain, data }: EditShortUrl): Promise<ShortUrl> =>
|
||||
apiClient.updateShortUrl(shortCode, domain, data as any) // FIXME parse dates
|
||||
,
|
||||
);
|
||||
|
||||
export const shortUrlEditionReducerCreator = (editShortUrlThunk: ReturnType<typeof editShortUrl>) => createSlice({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { assocPath, last, pipe, reject } from 'ramda';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../../api/types';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
||||
@ -24,11 +24,16 @@ const initialState: ShortUrlsList = {
|
||||
error: false,
|
||||
};
|
||||
|
||||
export const listShortUrls = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
|
||||
export const listShortUrls = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/listShortUrls`,
|
||||
(params: ShlinkShortUrlsListParams | void, { getState }): Promise<ShlinkShortUrlsResponse> => {
|
||||
const { listShortUrls: shlinkListShortUrls } = buildShlinkApiClient(getState);
|
||||
return shlinkListShortUrls(params ?? {});
|
||||
(params: ShlinkShortUrlsListParams | void): Promise<ShlinkShortUrlsResponse> => {
|
||||
try {
|
||||
const { listShortUrls: shlinkListShortUrls } = apiClient;
|
||||
return shlinkListShortUrls(params ?? {});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory('QrCodeModal', QrCodeModal, 'ImageDownloader');
|
||||
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ExportShortUrlsBtn', 'TagsSelector');
|
||||
|
||||
bottle.serviceFactory('ExportShortUrlsBtn', ExportShortUrlsBtn, 'buildShlinkApiClient', 'ReportExporter');
|
||||
bottle.serviceFactory('ExportShortUrlsBtn', ExportShortUrlsBtn, 'apiClient', 'ReportExporter');
|
||||
bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer']));
|
||||
|
||||
// Reducers
|
||||
@ -76,20 +76,20 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory('shortUrlDeletionReducerCreator', shortUrlDeletionReducerCreator, 'deleteShortUrl');
|
||||
bottle.serviceFactory('shortUrlDeletionReducer', prop('reducer'), 'shortUrlDeletionReducerCreator');
|
||||
|
||||
bottle.serviceFactory('shortUrlDetailReducerCreator', shortUrlDetailReducerCreator, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('shortUrlDetailReducerCreator', shortUrlDetailReducerCreator, 'apiClient');
|
||||
bottle.serviceFactory('shortUrlDetailReducer', prop('reducer'), 'shortUrlDetailReducerCreator');
|
||||
|
||||
// Actions
|
||||
bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('listShortUrls', listShortUrls, 'apiClient');
|
||||
|
||||
bottle.serviceFactory('createShortUrl', createShortUrl, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('createShortUrl', createShortUrl, 'apiClient');
|
||||
bottle.serviceFactory('resetCreateShortUrl', prop('resetCreateShortUrl'), 'shortUrlCreationReducerCreator');
|
||||
|
||||
bottle.serviceFactory('deleteShortUrl', deleteShortUrl, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('deleteShortUrl', deleteShortUrl, 'apiClient');
|
||||
bottle.serviceFactory('resetDeleteShortUrl', prop('resetDeleteShortUrl'), 'shortUrlDeletionReducerCreator');
|
||||
bottle.serviceFactory('shortUrlDeleted', () => shortUrlDeleted);
|
||||
|
||||
bottle.serviceFactory('getShortUrlDetail', prop('getShortUrlDetail'), 'shortUrlDetailReducerCreator');
|
||||
|
||||
bottle.serviceFactory('editShortUrl', editShortUrl, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('editShortUrl', editShortUrl, 'apiClient');
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -21,10 +21,9 @@ const initialState: TagDeletion = {
|
||||
|
||||
export const tagDeleted = createAction<string>(`${REDUCER_PREFIX}/tagDeleted`);
|
||||
|
||||
export const tagDeleteReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||
const deleteTag = createAsyncThunk(`${REDUCER_PREFIX}/deleteTag`, async (tag: string, { getState }): Promise<void> => {
|
||||
const { deleteTags } = buildShlinkApiClient(getState);
|
||||
await deleteTags([tag]);
|
||||
export const tagDeleteReducerCreator = (apiClient: ShlinkApiClient) => {
|
||||
const deleteTag = createAsyncThunk(`${REDUCER_PREFIX}/deleteTag`, async (tag: string): Promise<void> => {
|
||||
await apiClient.deleteTags([tag]);
|
||||
});
|
||||
|
||||
const { reducer } = createSlice({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { pick } from 'ramda';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
@ -35,12 +35,12 @@ const initialState: TagEdition = {
|
||||
export const tagEdited = createAction<EditTag>(`${REDUCER_PREFIX}/tagEdited`);
|
||||
|
||||
export const editTag = (
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||
apiClient: ShlinkApiClient,
|
||||
colorGenerator: ColorGenerator,
|
||||
) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/editTag`,
|
||||
async ({ oldName, newName, color }: EditTag, { getState }): Promise<EditTag> => {
|
||||
await buildShlinkApiClient(getState).editTag(oldName, newName);
|
||||
async ({ oldName, newName, color }: EditTag): Promise<EditTag> => {
|
||||
await apiClient.editTag(oldName, newName);
|
||||
colorGenerator.setColorForKey(newName, color);
|
||||
|
||||
return { oldName, newName, color };
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { isEmpty, reject } from 'ramda';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkTags } from '../../../api/types';
|
||||
import type { ProblemDetailsError } from '../../../api/types/errors';
|
||||
import { parseApiError } from '../../../api/utils';
|
||||
@ -84,7 +84,7 @@ const calculateVisitsPerTag = (createdVisits: CreateVisit[]): TagIncrease[] => O
|
||||
}, {}),
|
||||
);
|
||||
|
||||
export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = true) => createAsyncThunk(
|
||||
export const listTags = (apiClient: ShlinkApiClient, force = true) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/listTags`,
|
||||
async (_: void, { getState }): Promise<ListTags> => {
|
||||
const { tagsList, selectedServer } = getState();
|
||||
@ -93,11 +93,10 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t
|
||||
return tagsList;
|
||||
}
|
||||
|
||||
const { listTags: shlinkListTags, tagsStats } = buildShlinkApiClient(getState);
|
||||
const { tags, stats }: ShlinkTags = await (
|
||||
isReachableServer(selectedServer) && isFeatureEnabledForVersion('tagsStats', selectedServer.version)
|
||||
? tagsStats()
|
||||
: shlinkListTags()
|
||||
? apiClient.tagsStats()
|
||||
: apiClient.listTags()
|
||||
);
|
||||
const processedStats = stats.reduce<TagsStatsMap>((acc, { tag, ...rest }) => {
|
||||
acc[tag] = rest;
|
||||
|
||||
@ -36,15 +36,14 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory('tagEditReducerCreator', tagEditReducerCreator, 'editTag');
|
||||
bottle.serviceFactory('tagEditReducer', prop('reducer'), 'tagEditReducerCreator');
|
||||
|
||||
bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('tagDeleteReducerCreator', tagDeleteReducerCreator, 'apiClient');
|
||||
bottle.serviceFactory('tagDeleteReducer', prop('reducer'), 'tagDeleteReducerCreator');
|
||||
|
||||
bottle.serviceFactory('tagsListReducerCreator', tagsListReducerCreator, 'listTags', 'createShortUrl');
|
||||
bottle.serviceFactory('tagsListReducer', prop('reducer'), 'tagsListReducerCreator');
|
||||
|
||||
// Actions
|
||||
const listTagsActionFactory = (force: boolean) =>
|
||||
({ buildShlinkApiClient }: IContainer) => listTags(buildShlinkApiClient, force);
|
||||
const listTagsActionFactory = (force: boolean) => ({ apiClient }: IContainer) => listTags(apiClient, force);
|
||||
|
||||
bottle.factory('listTags', listTagsActionFactory(false));
|
||||
bottle.factory('forceListTags', listTagsActionFactory(true));
|
||||
@ -53,6 +52,6 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory('deleteTag', prop('deleteTag'), 'tagDeleteReducerCreator');
|
||||
bottle.serviceFactory('tagDeleted', () => tagDeleted);
|
||||
|
||||
bottle.serviceFactory('editTag', editTag, 'buildShlinkApiClient', 'ColorGenerator');
|
||||
bottle.serviceFactory('editTag', editTag, 'apiClient', 'ColorGenerator');
|
||||
bottle.serviceFactory('tagEdited', () => tagEdited);
|
||||
};
|
||||
|
||||
@ -22,7 +22,7 @@ type LastVisitLoader = (excludeBots?: boolean) => Promise<Visit | undefined>;
|
||||
|
||||
interface VisitsAsyncThunkOptions<T extends LoadVisits = LoadVisits, R extends VisitsLoaded = VisitsLoaded> {
|
||||
typePrefix: string;
|
||||
createLoaders: (params: T, getState: () => ShlinkState) => [VisitsLoader, LastVisitLoader];
|
||||
createLoaders: (params: T) => [VisitsLoader, LastVisitLoader];
|
||||
getExtraFulfilledPayload: (params: T) => Partial<R>;
|
||||
shouldCancel: (getState: () => ShlinkState) => boolean;
|
||||
}
|
||||
@ -35,7 +35,7 @@ export const createVisitsAsyncThunk = <T extends LoadVisits = LoadVisits, R exte
|
||||
const fallbackToInterval = createAction<DateInterval>(`${typePrefix}/fallbackToInterval`);
|
||||
|
||||
const asyncThunk = createAsyncThunk(typePrefix, async (params: T, { getState, dispatch }): Promise<Partial<R>> => {
|
||||
const [visitsLoader, lastVisitLoader] = createLoaders(params, getState);
|
||||
const [visitsLoader, lastVisitLoader] = createLoaders(params);
|
||||
|
||||
const loadVisitsInParallel = async (pages: number[]): Promise<Visit[]> =>
|
||||
Promise.all(pages.map(async (page) => visitsLoader(page, ITEMS_PER_PAGE).then(prop('data')))).then(flatten);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import { isBetween } from '../../../utils/helpers/date';
|
||||
import { domainMatches } from '../../short-urls/helpers';
|
||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||
@ -26,10 +26,10 @@ const initialState: DomainVisits = {
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export const getDomainVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => createVisitsAsyncThunk({
|
||||
export const getDomainVisits = (apiClient: ShlinkApiClient) => createVisitsAsyncThunk({
|
||||
typePrefix: `${REDUCER_PREFIX}/getDomainVisits`,
|
||||
createLoaders: ({ domain, query = {}, doIntervalFallback = false }: LoadDomainVisits, getState) => {
|
||||
const { getDomainVisits: getVisits } = buildShlinkApiClient(getState);
|
||||
createLoaders: ({ domain, query = {}, doIntervalFallback = false }: LoadDomainVisits) => {
|
||||
const { getDomainVisits: getVisits } = apiClient;
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits(
|
||||
domain,
|
||||
{ ...query, page, itemsPerPage },
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import { isBetween } from '../../../utils/helpers/date';
|
||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||
@ -14,10 +15,10 @@ const initialState: VisitsInfo = {
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export const getNonOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => createVisitsAsyncThunk({
|
||||
export const getNonOrphanVisits = (apiClient: ShlinkApiClient) => createVisitsAsyncThunk({
|
||||
typePrefix: `${REDUCER_PREFIX}/getNonOrphanVisits`,
|
||||
createLoaders: ({ query = {}, doIntervalFallback = false }, getState) => {
|
||||
const { getNonOrphanVisits: shlinkGetNonOrphanVisits } = buildShlinkApiClient(getState);
|
||||
createLoaders: ({ query = {}, doIntervalFallback = false }) => {
|
||||
const { getNonOrphanVisits: shlinkGetNonOrphanVisits } = apiClient;
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) =>
|
||||
shlinkGetNonOrphanVisits({ ...query, page, itemsPerPage });
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, shlinkGetNonOrphanVisits);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import { isBetween } from '../../../utils/helpers/date';
|
||||
import type { OrphanVisit, OrphanVisitType } from '../types';
|
||||
@ -23,10 +24,10 @@ const initialState: VisitsInfo = {
|
||||
const matchesType = (visit: OrphanVisit, orphanVisitsType?: OrphanVisitType) =>
|
||||
!orphanVisitsType || orphanVisitsType === visit.type;
|
||||
|
||||
export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => createVisitsAsyncThunk({
|
||||
export const getOrphanVisits = (apiClient: ShlinkApiClient) => createVisitsAsyncThunk({
|
||||
typePrefix: `${REDUCER_PREFIX}/getOrphanVisits`,
|
||||
createLoaders: ({ orphanVisitsType, query = {}, doIntervalFallback = false }: LoadOrphanVisits, getState) => {
|
||||
const { getOrphanVisits: getVisits } = buildShlinkApiClient(getState);
|
||||
createLoaders: ({ orphanVisitsType, query = {}, doIntervalFallback = false }: LoadOrphanVisits) => {
|
||||
const { getOrphanVisits: getVisits } = apiClient;
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits({ ...query, page, itemsPerPage })
|
||||
.then((result) => {
|
||||
const visits = result.data.filter((visit) => isOrphanVisit(visit) && matchesType(visit, orphanVisitsType));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import { isBetween } from '../../../utils/helpers/date';
|
||||
import type { ShortUrlIdentifier } from '../../short-urls/data';
|
||||
import { shortUrlMatches } from '../../short-urls/helpers';
|
||||
@ -24,10 +24,10 @@ const initialState: ShortUrlVisits = {
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => createVisitsAsyncThunk({
|
||||
export const getShortUrlVisits = (apiClient: ShlinkApiClient) => createVisitsAsyncThunk({
|
||||
typePrefix: `${REDUCER_PREFIX}/getShortUrlVisits`,
|
||||
createLoaders: ({ shortCode, query = {}, doIntervalFallback = false }: LoadShortUrlVisits, getState) => {
|
||||
const { getShortUrlVisits: shlinkGetShortUrlVisits } = buildShlinkApiClient(getState);
|
||||
createLoaders: ({ shortCode, query = {}, doIntervalFallback = false }: LoadShortUrlVisits) => {
|
||||
const { getShortUrlVisits: shlinkGetShortUrlVisits } = apiClient;
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) => shlinkGetShortUrlVisits(
|
||||
shortCode,
|
||||
{ ...query, page, itemsPerPage },
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import { isBetween } from '../../../utils/helpers/date';
|
||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||
import type { LoadVisits, VisitsInfo } from './types';
|
||||
@ -23,10 +23,10 @@ const initialState: TagVisits = {
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => createVisitsAsyncThunk({
|
||||
export const getTagVisits = (apiClient: ShlinkApiClient) => createVisitsAsyncThunk({
|
||||
typePrefix: `${REDUCER_PREFIX}/getTagVisits`,
|
||||
createLoaders: ({ tag, query = {}, doIntervalFallback = false }: LoadTagVisits, getState) => {
|
||||
const { getTagVisits: getVisits } = buildShlinkApiClient(getState);
|
||||
createLoaders: ({ tag, query = {}, doIntervalFallback = false }: LoadTagVisits) => {
|
||||
const { getTagVisits: getVisits } = apiClient;
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits(
|
||||
tag,
|
||||
{ ...query, page, itemsPerPage },
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder';
|
||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
||||
import type { ShlinkVisitsOverview } from '../../../api/types';
|
||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||
import type { CreateVisit } from '../types';
|
||||
@ -40,19 +40,19 @@ const initialState: VisitsOverview = {
|
||||
|
||||
const countBots = (visits: CreateVisit[]) => visits.filter(({ visit }) => visit.potentialBot).length;
|
||||
|
||||
export const loadVisitsOverview = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk(
|
||||
export const loadVisitsOverview = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/loadVisitsOverview`,
|
||||
(_: void, { getState }): Promise<ParsedVisitsOverview> => buildShlinkApiClient(getState).getVisitsOverview().then(
|
||||
(resp) => ({
|
||||
(): Promise<ParsedVisitsOverview> => apiClient.getVisitsOverview().then(
|
||||
({ nonOrphanVisits, visitsCount, orphanVisits, orphanVisitsCount }) => ({
|
||||
nonOrphanVisits: {
|
||||
total: resp.nonOrphanVisits?.total ?? resp.visitsCount,
|
||||
nonBots: resp.nonOrphanVisits?.nonBots,
|
||||
bots: resp.nonOrphanVisits?.bots,
|
||||
total: nonOrphanVisits?.total ?? visitsCount,
|
||||
nonBots: nonOrphanVisits?.nonBots,
|
||||
bots: nonOrphanVisits?.bots,
|
||||
},
|
||||
orphanVisits: {
|
||||
total: resp.orphanVisits?.total ?? resp.orphanVisitsCount,
|
||||
nonBots: resp.orphanVisits?.nonBots,
|
||||
bots: resp.orphanVisits?.bots,
|
||||
total: orphanVisits?.total ?? orphanVisitsCount,
|
||||
nonBots: orphanVisits?.nonBots,
|
||||
bots: orphanVisits?.bots,
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
@ -54,23 +54,23 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
bottle.serviceFactory('VisitsParser', () => visitsParser);
|
||||
|
||||
// Actions
|
||||
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getShortUrlVisits', getShortUrlVisits, 'apiClient');
|
||||
bottle.serviceFactory('cancelGetShortUrlVisits', prop('cancelGetVisits'), 'shortUrlVisitsReducerCreator');
|
||||
|
||||
bottle.serviceFactory('getTagVisits', getTagVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getTagVisits', getTagVisits, 'apiClient');
|
||||
bottle.serviceFactory('cancelGetTagVisits', prop('cancelGetVisits'), 'tagVisitsReducerCreator');
|
||||
|
||||
bottle.serviceFactory('getDomainVisits', getDomainVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getDomainVisits', getDomainVisits, 'apiClient');
|
||||
bottle.serviceFactory('cancelGetDomainVisits', prop('cancelGetVisits'), 'domainVisitsReducerCreator');
|
||||
|
||||
bottle.serviceFactory('getOrphanVisits', getOrphanVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getOrphanVisits', getOrphanVisits, 'apiClient');
|
||||
bottle.serviceFactory('cancelGetOrphanVisits', prop('cancelGetVisits'), 'orphanVisitsReducerCreator');
|
||||
|
||||
bottle.serviceFactory('getNonOrphanVisits', getNonOrphanVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('getNonOrphanVisits', getNonOrphanVisits, 'apiClient');
|
||||
bottle.serviceFactory('cancelGetNonOrphanVisits', prop('cancelGetVisits'), 'nonOrphanVisitsReducerCreator');
|
||||
|
||||
bottle.serviceFactory('createNewVisits', () => createNewVisits);
|
||||
bottle.serviceFactory('loadVisitsOverview', loadVisitsOverview, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('loadVisitsOverview', loadVisitsOverview, 'apiClient');
|
||||
|
||||
// Reducers
|
||||
bottle.serviceFactory('visitsOverviewReducerCreator', visitsOverviewReducerCreator, 'loadVisitsOverview');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user