From 5f6dc186e30ea0d99e546c00027a6b2010821294 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 24 Jul 2023 17:30:58 +0200 Subject: [PATCH] Define Shlink API contract --- src/api/ShlinkApiError.tsx | 4 +- src/api/services/ShlinkApiClient.ts | 18 +++--- src/common/MenuLayout.tsx | 1 + src/shlink-web-component/Main.tsx | 10 +-- .../ShlinkWebComponent.tsx | 10 ++- .../api-contract/ShlinkApiClient.ts | 62 +++++++++++++++++++ .../api-contract}/errors.ts | 0 .../api-contract/index.ts | 3 + .../api-contract/types.ts} | 4 +- .../api-contract/utils.ts} | 14 ++--- .../domains/reducers/domainRedirects.ts | 3 +- .../domains/reducers/domainsList.ts | 6 +- .../mercure/reducers/mercureInfo.ts | 3 +- .../overview/Overview.tsx | 1 - .../short-urls/Paginator.tsx | 9 +-- .../short-urls/ShortUrlsList.tsx | 11 +--- .../short-urls/ShortUrlsTable.tsx | 4 -- .../helpers/DeleteShortUrlModal.tsx | 2 +- .../short-urls/helpers/ExportShortUrlsBtn.tsx | 2 +- .../short-urls/helpers/ShortUrlDetailLink.tsx | 16 ++--- .../helpers/ShortUrlVisitsCount.tsx | 7 +-- .../short-urls/helpers/ShortUrlsRow.tsx | 8 +-- .../short-urls/helpers/ShortUrlsRowMenu.tsx | 8 +-- .../short-urls/reducers/shortUrlCreation.ts | 5 +- .../short-urls/reducers/shortUrlDeletion.ts | 5 +- .../short-urls/reducers/shortUrlDetail.ts | 5 +- .../short-urls/reducers/shortUrlEdition.ts | 5 +- .../short-urls/reducers/shortUrlsList.ts | 13 +--- .../short-urls/services/provideServices.ts | 2 +- .../tags/reducers/tagDelete.ts | 5 +- .../tags/reducers/tagEdit.ts | 5 +- .../tags/reducers/tagsList.ts | 6 +- .../utils/routesPrefix.ts | 7 +++ .../visits/DomainVisits.tsx | 2 +- .../visits/reducers/common.ts | 4 +- .../visits/reducers/domainVisits.ts | 2 +- .../visits/reducers/nonOrphanVisits.ts | 2 +- .../visits/reducers/orphanVisits.ts | 3 +- .../visits/reducers/shortUrlVisits.ts | 2 +- .../visits/reducers/tagVisits.ts | 2 +- .../visits/reducers/types/index.ts | 3 +- .../visits/reducers/visitsOverview.ts | 3 +- 42 files changed, 161 insertions(+), 126 deletions(-) create mode 100644 src/shlink-web-component/api-contract/ShlinkApiClient.ts rename src/{api/types => shlink-web-component/api-contract}/errors.ts (100%) create mode 100644 src/shlink-web-component/api-contract/index.ts rename src/{api/types/index.ts => shlink-web-component/api-contract/types.ts} (95%) rename src/{api/utils/index.ts => shlink-web-component/api-contract/utils.ts} (93%) create mode 100644 src/shlink-web-component/utils/routesPrefix.ts diff --git a/src/api/ShlinkApiError.tsx b/src/api/ShlinkApiError.tsx index 057641da..a16d97d2 100644 --- a/src/api/ShlinkApiError.tsx +++ b/src/api/ShlinkApiError.tsx @@ -1,5 +1,5 @@ -import type { ProblemDetailsError } from './types/errors'; -import { isInvalidArgumentError } from './utils'; +import type { ProblemDetailsError } from '../shlink-web-component/api-contract'; +import { isInvalidArgumentError } from '../shlink-web-component/api-contract/utils'; export interface ShlinkApiErrorProps { errorData?: ProblemDetailsError; diff --git a/src/api/services/ShlinkApiClient.ts b/src/api/services/ShlinkApiClient.ts index b290f6be..d1e22d2e 100644 --- a/src/api/services/ShlinkApiClient.ts +++ b/src/api/services/ShlinkApiClient.ts @@ -1,11 +1,7 @@ import { isEmpty, isNil, reject } from 'ramda'; import type { HttpClient } from '../../common/services/HttpClient'; -import type { ShortUrl, ShortUrlData } from '../../shlink-web-component/short-urls/data'; -import { orderToString } from '../../utils/helpers/ordering'; -import { stringifyQuery } from '../../utils/helpers/query'; -import { replaceAuthorityFromUri } from '../../utils/helpers/uri'; -import type { OptionalString } from '../../utils/utils'; import type { + ShlinkApiClient as BaseShlinkApiClient, ShlinkDomainRedirects, ShlinkDomainsResponse, ShlinkEditDomainRedirects, @@ -20,9 +16,13 @@ import type { ShlinkTagsStatsResponse, ShlinkVisits, ShlinkVisitsOverview, - ShlinkVisitsParams, -} from '../types'; -import { isRegularNotFound, parseApiError } from '../utils'; + ShlinkVisitsParams } from '../../shlink-web-component/api-contract'; +import { isRegularNotFound, parseApiError } from '../../shlink-web-component/api-contract/utils'; +import type { ShortUrl, ShortUrlData } from '../../shlink-web-component/short-urls/data'; +import { orderToString } from '../../utils/helpers/ordering'; +import { stringifyQuery } from '../../utils/helpers/query'; +import { replaceAuthorityFromUri } from '../../utils/helpers/uri'; +import type { OptionalString } from '../../utils/utils'; type ApiVersion = 2 | 3; @@ -45,7 +45,7 @@ const normalizeListParams = ( orderBy: orderToString(orderBy), }); -export class ShlinkApiClient { +export class ShlinkApiClient implements BaseShlinkApiClient { private apiVersion: ApiVersion; public constructor( diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 42149b6f..39891ea6 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -13,6 +13,7 @@ interface MenuLayoutProps { settings: Settings; } +// FIXME Rename this to something else export const MenuLayout = ( buildShlinkApiClient: ShlinkApiClientBuilder, ServerError: FC, diff --git a/src/shlink-web-component/Main.tsx b/src/shlink-web-component/Main.tsx index a15980ed..260aaffd 100644 --- a/src/shlink-web-component/Main.tsx +++ b/src/shlink-web-component/Main.tsx @@ -8,10 +8,7 @@ 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; -}; +import { useRoutesPrefix } from './utils/routesPrefix'; export const Main = ( TagsList: FC, @@ -25,8 +22,9 @@ export const Main = ( Overview: FC, EditShortUrl: FC, ManageDomains: FC, -): FC => ({ routesPrefix = '' }) => { +): FC => () => { const location = useLocation(); + const routesPrefix = useRoutesPrefix(); const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle(); useEffect(() => hideSidebar(), [location]); @@ -71,5 +69,3 @@ export const Main = ( ); }; - -export type MainType = ReturnType; diff --git a/src/shlink-web-component/ShlinkWebComponent.tsx b/src/shlink-web-component/ShlinkWebComponent.tsx index 778ea8ba..57c07742 100644 --- a/src/shlink-web-component/ShlinkWebComponent.tsx +++ b/src/shlink-web-component/ShlinkWebComponent.tsx @@ -4,8 +4,10 @@ import type { FC, ReactNode } from 'react'; import { useEffect, useRef, useState } from 'react'; import { Provider } from 'react-redux'; import type { SemVer } from '../utils/helpers/version'; +import type { ShlinkApiClient } from './api-contract'; import { setUpStore } from './container/store'; import { FeaturesProvider, useFeatures } from './utils/features'; +import { RoutesPrefixProvider } from './utils/routesPrefix'; import type { Settings } from './utils/settings'; import { SettingsProvider } from './utils/settings'; @@ -13,7 +15,7 @@ type ShlinkWebComponentProps = { routesPrefix?: string; settings?: Settings; serverVersion: SemVer; - apiClient: any; + apiClient: ShlinkApiClient; }; export const createShlinkWebComponent = ( @@ -30,7 +32,7 @@ export const createShlinkWebComponent = ( // depend on it const { container } = bottle; const { Main } = container; - mainContent.current =
; + mainContent.current =
; setStore(setUpStore(container)); }, []); @@ -38,7 +40,9 @@ export const createShlinkWebComponent = ( - {mainContent.current} + + {mainContent.current} + diff --git a/src/shlink-web-component/api-contract/ShlinkApiClient.ts b/src/shlink-web-component/api-contract/ShlinkApiClient.ts new file mode 100644 index 00000000..02b8acca --- /dev/null +++ b/src/shlink-web-component/api-contract/ShlinkApiClient.ts @@ -0,0 +1,62 @@ +import type { ShortUrl, ShortUrlData } from '../short-urls/data'; +import type { + ShlinkDomainRedirects, + ShlinkDomainsResponse, + ShlinkEditDomainRedirects, + ShlinkHealth, + ShlinkMercureInfo, + ShlinkShortUrlData, + ShlinkShortUrlsListParams, + ShlinkShortUrlsResponse, + ShlinkTags, + ShlinkVisits, + ShlinkVisitsOverview, + ShlinkVisitsParams, +} from './types'; + +export type ShlinkApiClient = { + baseUrl: string; + apiKey: string; + + listShortUrls(params?: ShlinkShortUrlsListParams): Promise; + + createShortUrl(options: ShortUrlData): Promise; + + getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise; + + getTagVisits(tag: string, query?: Omit): Promise; + + getDomainVisits(domain: string, query?: Omit): Promise; + + getOrphanVisits(query?: Omit): Promise; + + getNonOrphanVisits(query?: Omit): Promise; + + getVisitsOverview(): Promise; + + getShortUrl(shortCode: string, domain?: string | null): Promise; + + deleteShortUrl(shortCode: string, domain?: string | null): Promise; + + updateShortUrl( + shortCode: string, + domain: string | null | undefined, + body: ShlinkShortUrlData, + ): Promise; + + listTags(): Promise; + + tagsStats(): Promise; + + deleteTags(tags: string[]): Promise<{ tags: string[] }>; + + editTag(oldName: string, newName: string): Promise<{ oldName: string; newName: string }>; + + health(authority?: string): Promise; + + mercureInfo(): Promise; + + listDomains(): Promise; + + editDomainRedirects(domainRedirects: ShlinkEditDomainRedirects): Promise; +}; diff --git a/src/api/types/errors.ts b/src/shlink-web-component/api-contract/errors.ts similarity index 100% rename from src/api/types/errors.ts rename to src/shlink-web-component/api-contract/errors.ts diff --git a/src/shlink-web-component/api-contract/index.ts b/src/shlink-web-component/api-contract/index.ts new file mode 100644 index 00000000..850275d8 --- /dev/null +++ b/src/shlink-web-component/api-contract/index.ts @@ -0,0 +1,3 @@ +export * from './errors'; +export * from './ShlinkApiClient'; +export * from './types'; diff --git a/src/api/types/index.ts b/src/shlink-web-component/api-contract/types.ts similarity index 95% rename from src/api/types/index.ts rename to src/shlink-web-component/api-contract/types.ts index eb4a7be8..65b91718 100644 --- a/src/api/types/index.ts +++ b/src/shlink-web-component/api-contract/types.ts @@ -1,7 +1,7 @@ -import type { ShortUrl, ShortUrlMeta } from '../../shlink-web-component/short-urls/data'; -import type { Visit } from '../../shlink-web-component/visits/types'; import type { Order } from '../../utils/helpers/ordering'; import type { OptionalString } from '../../utils/utils'; +import type { ShortUrl, ShortUrlMeta } from '../short-urls/data'; +import type { Visit } from '../visits/types'; export interface ShlinkShortUrlsResponse { data: ShortUrl[]; diff --git a/src/api/utils/index.ts b/src/shlink-web-component/api-contract/utils.ts similarity index 93% rename from src/api/utils/index.ts rename to src/shlink-web-component/api-contract/utils.ts index f9af97fe..0d24c81d 100644 --- a/src/api/utils/index.ts +++ b/src/shlink-web-component/api-contract/utils.ts @@ -2,16 +2,11 @@ import type { InvalidArgumentError, InvalidShortUrlDeletion, ProblemDetailsError, - RegularNotFound } from '../types/errors'; + RegularNotFound } from './errors'; import { ErrorTypeV2, ErrorTypeV3, -} from '../types/errors'; - -const isProblemDetails = (e: unknown): e is ProblemDetailsError => - !!e && typeof e === 'object' && ['type', 'detail', 'title', 'status'].every((prop) => prop in e); - -export const parseApiError = (e: unknown): ProblemDetailsError | undefined => (isProblemDetails(e) ? e : undefined); +} from './errors'; export const isInvalidArgumentError = (error?: ProblemDetailsError): error is InvalidArgumentError => error?.type === ErrorTypeV2.INVALID_ARGUMENT || error?.type === ErrorTypeV3.INVALID_ARGUMENT; @@ -23,3 +18,8 @@ export const isInvalidDeletionError = (error?: ProblemDetailsError): error is In export const isRegularNotFound = (error?: ProblemDetailsError): error is RegularNotFound => (error?.type === ErrorTypeV2.NOT_FOUND || error?.type === ErrorTypeV3.NOT_FOUND) && error?.status === 404; + +const isProblemDetails = (e: unknown): e is ProblemDetailsError => + !!e && typeof e === 'object' && ['type', 'detail', 'title', 'status'].every((prop) => prop in e); + +export const parseApiError = (e: unknown): ProblemDetailsError | undefined => (isProblemDetails(e) ? e : undefined); diff --git a/src/shlink-web-component/domains/reducers/domainRedirects.ts b/src/shlink-web-component/domains/reducers/domainRedirects.ts index c1db9f6c..342fe6a2 100644 --- a/src/shlink-web-component/domains/reducers/domainRedirects.ts +++ b/src/shlink-web-component/domains/reducers/domainRedirects.ts @@ -1,6 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; -import type { ShlinkDomainRedirects } from '../../../api/types'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ShlinkApiClient, ShlinkDomainRedirects } from '../../api-contract'; const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS'; diff --git a/src/shlink-web-component/domains/reducers/domainsList.ts b/src/shlink-web-component/domains/reducers/domainsList.ts index af04da84..708165c6 100644 --- a/src/shlink-web-component/domains/reducers/domainsList.ts +++ b/src/shlink-web-component/domains/reducers/domainsList.ts @@ -1,10 +1,8 @@ import type { AsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit'; import { createAction, createSlice } from '@reduxjs/toolkit'; -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 { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ProblemDetailsError, ShlinkApiClient, ShlinkDomainRedirects } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { Domain, DomainStatus } from '../data'; import type { EditDomainRedirects } from './domainRedirects'; diff --git a/src/shlink-web-component/mercure/reducers/mercureInfo.ts b/src/shlink-web-component/mercure/reducers/mercureInfo.ts index 2ac3f144..07d50c3a 100644 --- a/src/shlink-web-component/mercure/reducers/mercureInfo.ts +++ b/src/shlink-web-component/mercure/reducers/mercureInfo.ts @@ -1,7 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; -import type { ShlinkMercureInfo } from '../../../api/types'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ShlinkApiClient, ShlinkMercureInfo } from '../../api-contract'; const REDUCER_PREFIX = 'shlink/mercure'; diff --git a/src/shlink-web-component/overview/Overview.tsx b/src/shlink-web-component/overview/Overview.tsx index 9c1d97d7..717bc872 100644 --- a/src/shlink-web-component/overview/Overview.tsx +++ b/src/shlink-web-component/overview/Overview.tsx @@ -107,7 +107,6 @@ export const Overview = ( navigate(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)} /> diff --git a/src/shlink-web-component/short-urls/Paginator.tsx b/src/shlink-web-component/short-urls/Paginator.tsx index 3b083bed..f1a2e0b4 100644 --- a/src/shlink-web-component/short-urls/Paginator.tsx +++ b/src/shlink-web-component/short-urls/Paginator.tsx @@ -1,6 +1,5 @@ import { Link } from 'react-router-dom'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; -import type { ShlinkPaginator } from '../../api/types'; import type { NumberOrEllipsis } from '../../utils/helpers/pagination'; import { @@ -9,17 +8,19 @@ import { prettifyPageNumber, progressivePagination, } from '../../utils/helpers/pagination'; +import type { ShlinkPaginator } from '../api-contract'; +import { useRoutesPrefix } from '../utils/routesPrefix'; interface PaginatorProps { paginator?: ShlinkPaginator; - serverId: string; currentQueryString?: string; } -export const Paginator = ({ paginator, serverId, currentQueryString = '' }: PaginatorProps) => { +export const Paginator = ({ paginator, currentQueryString = '' }: PaginatorProps) => { const { currentPage = 0, pagesCount = 0 } = paginator ?? {}; + const routesPrefix = useRoutesPrefix(); const urlForPage = (pageNumber: NumberOrEllipsis) => - `/server/${serverId}/list-short-urls/${pageNumber}${currentQueryString}`; + `${routesPrefix}/list-short-urls/${pageNumber}${currentQueryString}`; if (pagesCount <= 1) { return
; // Return some space diff --git a/src/shlink-web-component/short-urls/ShortUrlsList.tsx b/src/shlink-web-component/short-urls/ShortUrlsList.tsx index b385cf9b..ba84dd5d 100644 --- a/src/shlink-web-component/short-urls/ShortUrlsList.tsx +++ b/src/shlink-web-component/short-urls/ShortUrlsList.tsx @@ -2,13 +2,11 @@ import { pipe } from 'ramda'; import { useEffect, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; import { Card } from 'reactstrap'; -import type { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../../api/types'; -import type { SelectedServer } from '../../servers/data'; -import { getServerId } from '../../servers/data'; import { DEFAULT_SHORT_URLS_ORDERING } from '../../settings/reducers/settings'; import type { OrderDir } from '../../utils/helpers/ordering'; import { determineOrderDir } from '../../utils/helpers/ordering'; import { TableOrderIcon } from '../../utils/table/TableOrderIcon'; +import type { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../api-contract'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { Topics } from '../mercure/helpers/Topics'; import { useFeature } from '../utils/features'; @@ -21,7 +19,6 @@ import type { ShortUrlsFilteringBarType } from './ShortUrlsFilteringBar'; import type { ShortUrlsTableType } from './ShortUrlsTable'; interface ShortUrlsListProps { - selectedServer: SelectedServer; shortUrlsList: ShortUrlsListState; listShortUrls: (params: ShlinkShortUrlsListParams) => void; } @@ -29,8 +26,7 @@ interface ShortUrlsListProps { export const ShortUrlsList = ( ShortUrlsTable: ShortUrlsTableType, ShortUrlsFilteringBar: ShortUrlsFilteringBarType, -) => boundToMercureHub(({ listShortUrls, shortUrlsList, selectedServer }: ShortUrlsListProps) => { - const serverId = getServerId(selectedServer); +) => boundToMercureHub(({ listShortUrls, shortUrlsList }: ShortUrlsListProps) => { const { page } = useParams(); const location = useLocation(); const [filter, toFirstPage] = useShortUrlsQuery(); @@ -108,13 +104,12 @@ export const ShortUrlsList = ( /> - + ); diff --git a/src/shlink-web-component/short-urls/ShortUrlsTable.tsx b/src/shlink-web-component/short-urls/ShortUrlsTable.tsx index 438081f2..53d09f59 100644 --- a/src/shlink-web-component/short-urls/ShortUrlsTable.tsx +++ b/src/shlink-web-component/short-urls/ShortUrlsTable.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; import { isEmpty } from 'ramda'; import type { ReactNode } from 'react'; -import type { SelectedServer } from '../../servers/data'; import type { ShortUrlsOrderableFields } from './data'; import type { ShortUrlsRowType } from './helpers/ShortUrlsRow'; import type { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; @@ -11,7 +10,6 @@ interface ShortUrlsTableProps { orderByColumn?: (column: ShortUrlsOrderableFields) => () => void; renderOrderIcon?: (column: ShortUrlsOrderableFields) => ReactNode; shortUrlsList: ShortUrlsListState; - selectedServer: SelectedServer; onTagClick?: (tag: string) => void; className?: string; } @@ -21,7 +19,6 @@ export const ShortUrlsTable = (ShortUrlsRow: ShortUrlsRowType) => ({ renderOrderIcon, shortUrlsList, onTagClick, - selectedServer, className, }: ShortUrlsTableProps) => { const { error, loading, shortUrls } = shortUrlsList; @@ -52,7 +49,6 @@ export const ShortUrlsTable = (ShortUrlsRow: ShortUrlsRowType) => ({ )); diff --git a/src/shlink-web-component/short-urls/helpers/DeleteShortUrlModal.tsx b/src/shlink-web-component/short-urls/helpers/DeleteShortUrlModal.tsx index 916af0f5..2a3697a3 100644 --- a/src/shlink-web-component/short-urls/helpers/DeleteShortUrlModal.tsx +++ b/src/shlink-web-component/short-urls/helpers/DeleteShortUrlModal.tsx @@ -2,9 +2,9 @@ import { pipe } from 'ramda'; import { useEffect, useState } from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { ShlinkApiError } from '../../../api/ShlinkApiError'; -import { isInvalidDeletionError } from '../../../api/utils'; import { Result } from '../../../utils/Result'; import { handleEventPreventingDefault } from '../../../utils/utils'; +import { isInvalidDeletionError } from '../../api-contract/utils'; import type { ShortUrlIdentifier, ShortUrlModalProps } from '../data'; import type { ShortUrlDeletion } from '../reducers/shortUrlDeletion'; diff --git a/src/shlink-web-component/short-urls/helpers/ExportShortUrlsBtn.tsx b/src/shlink-web-component/short-urls/helpers/ExportShortUrlsBtn.tsx index 0ba5839a..ec7b4497 100644 --- a/src/shlink-web-component/short-urls/helpers/ExportShortUrlsBtn.tsx +++ b/src/shlink-web-component/short-urls/helpers/ExportShortUrlsBtn.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react'; import { useCallback } from 'react'; -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'; import { ExportBtn } from '../../../utils/ExportBtn'; import { useToggle } from '../../../utils/helpers/hooks'; +import type { ShlinkApiClient } from '../../api-contract'; import type { ShortUrl } from '../data'; import { useShortUrlsQuery } from './hooks'; diff --git a/src/shlink-web-component/short-urls/helpers/ShortUrlDetailLink.tsx b/src/shlink-web-component/short-urls/helpers/ShortUrlDetailLink.tsx index bae54410..e39c9e57 100644 --- a/src/shlink-web-component/short-urls/helpers/ShortUrlDetailLink.tsx +++ b/src/shlink-web-component/short-urls/helpers/ShortUrlDetailLink.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; import { Link } from 'react-router-dom'; -import type { SelectedServer, ServerWithId } from '../../../servers/data'; -import { isServerWithId } from '../../../servers/data'; +import { useRoutesPrefix } from '../../utils/routesPrefix'; import type { ShortUrl } from '../data'; import { urlEncodeShortCode } from './index'; @@ -9,21 +8,22 @@ export type LinkSuffix = 'visits' | 'edit'; export interface ShortUrlDetailLinkProps { shortUrl?: ShortUrl | null; - selectedServer?: SelectedServer; suffix: LinkSuffix; + asLink?: boolean; } -const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => { +const buildUrl = (routePrefix: string, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => { const query = domain ? `?domain=${domain}` : ''; - return `/server/${id}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`; + return `${routePrefix}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`; }; export const ShortUrlDetailLink: FC> = ( - { selectedServer, shortUrl, suffix, children, ...rest }, + { shortUrl, suffix, asLink, children, ...rest }, ) => { - if (!selectedServer || !isServerWithId(selectedServer) || !shortUrl) { + const routePrefix = useRoutesPrefix(); + if (!asLink || !shortUrl) { return {children}; } - return {children}; + return {children}; }; diff --git a/src/shlink-web-component/short-urls/helpers/ShortUrlVisitsCount.tsx b/src/shlink-web-component/short-urls/helpers/ShortUrlVisitsCount.tsx index c0ed28ec..edc68338 100644 --- a/src/shlink-web-component/short-urls/helpers/ShortUrlVisitsCount.tsx +++ b/src/shlink-web-component/short-urls/helpers/ShortUrlVisitsCount.tsx @@ -2,7 +2,6 @@ import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { UncontrolledTooltip } from 'reactstrap'; -import type { SelectedServer } from '../../../servers/data'; import { formatHumanFriendly, parseISO } from '../../../utils/helpers/date'; import { useElementRef } from '../../../utils/helpers/hooks'; import { prettify } from '../../../utils/helpers/numbers'; @@ -12,18 +11,18 @@ import './ShortUrlVisitsCount.scss'; interface ShortUrlVisitsCountProps { shortUrl?: ShortUrl | null; - selectedServer?: SelectedServer; visitsCount: number; active?: boolean; + asLink?: boolean; } export const ShortUrlVisitsCount = ( - { visitsCount, shortUrl, selectedServer, active = false }: ShortUrlVisitsCountProps, + { visitsCount, shortUrl, active = false, asLink = false }: ShortUrlVisitsCountProps, ) => { const { maxVisits, validSince, validUntil } = shortUrl?.meta ?? {}; const hasLimit = !!maxVisits || !!validSince || !!validUntil; const visitsLink = ( - + diff --git a/src/shlink-web-component/short-urls/helpers/ShortUrlsRow.tsx b/src/shlink-web-component/short-urls/helpers/ShortUrlsRow.tsx index f783fc83..b83b109d 100644 --- a/src/shlink-web-component/short-urls/helpers/ShortUrlsRow.tsx +++ b/src/shlink-web-component/short-urls/helpers/ShortUrlsRow.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; import { useEffect, useRef } from 'react'; import { ExternalLink } from 'react-external-link'; -import type { SelectedServer } from '../../../servers/data'; import { CopyToClipboardIcon } from '../../../utils/CopyToClipboardIcon'; import { Time } from '../../../utils/dates/Time'; import type { TimeoutToggle } from '../../../utils/helpers/hooks'; @@ -17,7 +16,6 @@ import './ShortUrlsRow.scss'; interface ShortUrlsRowProps { onTagClick?: (tag: string) => void; - selectedServer: SelectedServer; shortUrl: ShortUrl; } @@ -27,7 +25,7 @@ export const ShortUrlsRow = ( ShortUrlsRowMenu: ShortUrlsRowMenuType, colorGenerator: ColorGenerator, useTimeoutToggle: TimeoutToggle, -) => ({ shortUrl, selectedServer, onTagClick }: ShortUrlsRowProps) => { +) => ({ shortUrl, onTagClick }: ShortUrlsRowProps) => { const [copiedToClipboard, setCopiedToClipboard] = useTimeoutToggle(); const [active, setActive] = useTimeoutToggle(false, 500); const isFirstRun = useRef(true); @@ -76,15 +74,15 @@ export const ShortUrlsRow = ( doExcludeBots ? shortUrl.visitsSummary?.nonBots : shortUrl.visitsSummary?.total ) ?? shortUrl.visitsCount} shortUrl={shortUrl} - selectedServer={selectedServer} active={active} + asLink /> - + ); diff --git a/src/shlink-web-component/short-urls/helpers/ShortUrlsRowMenu.tsx b/src/shlink-web-component/short-urls/helpers/ShortUrlsRowMenu.tsx index 3502ed26..30fb6bca 100644 --- a/src/shlink-web-component/short-urls/helpers/ShortUrlsRowMenu.tsx +++ b/src/shlink-web-component/short-urls/helpers/ShortUrlsRowMenu.tsx @@ -7,14 +7,12 @@ import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import type { FC } from 'react'; import { DropdownItem } from 'reactstrap'; -import type { SelectedServer } from '../../../servers/data'; import { useToggle } from '../../../utils/helpers/hooks'; import { RowDropdownBtn } from '../../../utils/RowDropdownBtn'; import type { ShortUrl, ShortUrlModalProps } from '../data'; import { ShortUrlDetailLink } from './ShortUrlDetailLink'; interface ShortUrlsRowMenuProps { - selectedServer: SelectedServer; shortUrl: ShortUrl; } type ShortUrlModal = FC; @@ -22,17 +20,17 @@ type ShortUrlModal = FC; export const ShortUrlsRowMenu = ( DeleteShortUrlModal: ShortUrlModal, QrCodeModal: ShortUrlModal, -) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => { +) => ({ shortUrl }: ShortUrlsRowMenuProps) => { const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle(); const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle(); return ( - + Visit stats - + Edit short URL diff --git a/src/shlink-web-component/short-urls/reducers/shortUrlCreation.ts b/src/shlink-web-component/short-urls/reducers/shortUrlCreation.ts index 777f02f5..2c5a1060 100644 --- a/src/shlink-web-component/short-urls/reducers/shortUrlCreation.ts +++ b/src/shlink-web-component/short-urls/reducers/shortUrlCreation.ts @@ -1,9 +1,8 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -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'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { ShortUrl, ShortUrlData } from '../data'; const REDUCER_PREFIX = 'shlink/shortUrlCreation'; diff --git a/src/shlink-web-component/short-urls/reducers/shortUrlDeletion.ts b/src/shlink-web-component/short-urls/reducers/shortUrlDeletion.ts index 6a7c4591..ceea27d7 100644 --- a/src/shlink-web-component/short-urls/reducers/shortUrlDeletion.ts +++ b/src/shlink-web-component/short-urls/reducers/shortUrlDeletion.ts @@ -1,8 +1,7 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; -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'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { ShortUrl, ShortUrlIdentifier } from '../data'; const REDUCER_PREFIX = 'shlink/shortUrlDeletion'; diff --git a/src/shlink-web-component/short-urls/reducers/shortUrlDetail.ts b/src/shlink-web-component/short-urls/reducers/shortUrlDetail.ts index 2b08d267..5da21087 100644 --- a/src/shlink-web-component/short-urls/reducers/shortUrlDetail.ts +++ b/src/shlink-web-component/short-urls/reducers/shortUrlDetail.ts @@ -1,9 +1,8 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -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'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { ShortUrl, ShortUrlIdentifier } from '../data'; import { shortUrlMatches } from '../helpers'; diff --git a/src/shlink-web-component/short-urls/reducers/shortUrlEdition.ts b/src/shlink-web-component/short-urls/reducers/shortUrlEdition.ts index 674e9d91..9025064c 100644 --- a/src/shlink-web-component/short-urls/reducers/shortUrlEdition.ts +++ b/src/shlink-web-component/short-urls/reducers/shortUrlEdition.ts @@ -1,9 +1,8 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -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'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { EditShortUrlData, ShortUrl, ShortUrlIdentifier } from '../data'; const REDUCER_PREFIX = 'shlink/shortUrlEdition'; diff --git a/src/shlink-web-component/short-urls/reducers/shortUrlsList.ts b/src/shlink-web-component/short-urls/reducers/shortUrlsList.ts index ade20b4b..d76064f3 100644 --- a/src/shlink-web-component/short-urls/reducers/shortUrlsList.ts +++ b/src/shlink-web-component/short-urls/reducers/shortUrlsList.ts @@ -1,8 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { assocPath, last, pipe, reject } from 'ramda'; -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; -import type { ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../../api/types'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ShlinkApiClient, ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../api-contract'; import { createNewVisits } from '../../visits/reducers/visitCreation'; import type { ShortUrl } from '../data'; import { shortUrlMatches } from '../helpers'; @@ -26,15 +25,7 @@ const initialState: ShortUrlsList = { export const listShortUrls = (apiClient: ShlinkApiClient) => createAsyncThunk( `${REDUCER_PREFIX}/listShortUrls`, - (params: ShlinkShortUrlsListParams | void): Promise => { - try { - const { listShortUrls: shlinkListShortUrls } = apiClient; - return shlinkListShortUrls(params ?? {}); - } catch (e) { - console.log(e); - throw e; - } - }, + (params: ShlinkShortUrlsListParams | void): Promise => apiClient.listShortUrls(params ?? {}), ); export const shortUrlsListReducerCreator = ( diff --git a/src/shlink-web-component/short-urls/services/provideServices.ts b/src/shlink-web-component/short-urls/services/provideServices.ts index 10e6fe45..c3896f2a 100644 --- a/src/shlink-web-component/short-urls/services/provideServices.ts +++ b/src/shlink-web-component/short-urls/services/provideServices.ts @@ -23,7 +23,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable', 'ShortUrlsFilteringBar'); bottle.decorator('ShortUrlsList', connect( - ['selectedServer', 'mercureInfo', 'shortUrlsList'], + ['mercureInfo', 'shortUrlsList'], ['listShortUrls', 'createNewVisits', 'loadMercureInfo'], )); diff --git a/src/shlink-web-component/tags/reducers/tagDelete.ts b/src/shlink-web-component/tags/reducers/tagDelete.ts index d87bc0fc..5b75941b 100644 --- a/src/shlink-web-component/tags/reducers/tagDelete.ts +++ b/src/shlink-web-component/tags/reducers/tagDelete.ts @@ -1,8 +1,7 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; -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'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; const REDUCER_PREFIX = 'shlink/tagDelete'; diff --git a/src/shlink-web-component/tags/reducers/tagEdit.ts b/src/shlink-web-component/tags/reducers/tagEdit.ts index 410c9421..8350e6d8 100644 --- a/src/shlink-web-component/tags/reducers/tagEdit.ts +++ b/src/shlink-web-component/tags/reducers/tagEdit.ts @@ -1,11 +1,10 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createAction, createSlice } from '@reduxjs/toolkit'; import { pick } from 'ramda'; -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'; import type { ColorGenerator } from '../../../utils/services/ColorGenerator'; +import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; const REDUCER_PREFIX = 'shlink/tagEdit'; diff --git a/src/shlink-web-component/tags/reducers/tagsList.ts b/src/shlink-web-component/tags/reducers/tagsList.ts index e5ff049d..1f994712 100644 --- a/src/shlink-web-component/tags/reducers/tagsList.ts +++ b/src/shlink-web-component/tags/reducers/tagsList.ts @@ -1,11 +1,9 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; import { isEmpty, reject } from 'ramda'; -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'; import { isReachableServer } from '../../../servers/data'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ProblemDetailsError, ShlinkApiClient, ShlinkTags } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { createShortUrl } from '../../short-urls/reducers/shortUrlCreation'; import { isFeatureEnabledForVersion } from '../../utils/features'; import { createNewVisits } from '../../visits/reducers/visitCreation'; diff --git a/src/shlink-web-component/utils/routesPrefix.ts b/src/shlink-web-component/utils/routesPrefix.ts new file mode 100644 index 00000000..90e943c7 --- /dev/null +++ b/src/shlink-web-component/utils/routesPrefix.ts @@ -0,0 +1,7 @@ +import { createContext, useContext } from 'react'; + +const RoutesPrefixContext = createContext(''); + +export const RoutesPrefixProvider = RoutesPrefixContext.Provider; + +export const useRoutesPrefix = (): string => useContext(RoutesPrefixContext); diff --git a/src/shlink-web-component/visits/DomainVisits.tsx b/src/shlink-web-component/visits/DomainVisits.tsx index 2c669e64..68710248 100644 --- a/src/shlink-web-component/visits/DomainVisits.tsx +++ b/src/shlink-web-component/visits/DomainVisits.tsx @@ -1,7 +1,7 @@ import { useParams } from 'react-router-dom'; -import type { ShlinkVisitsParams } from '../../api/types'; import type { ReportExporter } from '../../common/services/ReportExporter'; import { useGoBack } from '../../utils/helpers/hooks'; +import type { ShlinkVisitsParams } from '../api-contract'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { Topics } from '../mercure/helpers/Topics'; import type { DomainVisits as DomainVisitsState, LoadDomainVisits } from './reducers/domainVisits'; diff --git a/src/shlink-web-component/visits/reducers/common.ts b/src/shlink-web-component/visits/reducers/common.ts index 0c57cb3c..6325e63d 100644 --- a/src/shlink-web-component/visits/reducers/common.ts +++ b/src/shlink-web-component/visits/reducers/common.ts @@ -1,11 +1,11 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; import { flatten, prop, range, splitEvery } from 'ramda'; -import type { ShlinkPaginator, ShlinkVisits, ShlinkVisitsParams } from '../../../api/types'; -import { parseApiError } from '../../../api/utils'; import type { ShlinkState } from '../../../container/types'; import type { DateInterval } from '../../../utils/helpers/dateIntervals'; import { dateToMatchingInterval } from '../../../utils/helpers/dateIntervals'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ShlinkPaginator, ShlinkVisits, ShlinkVisitsParams } from '../../api-contract'; +import { parseApiError } from '../../api-contract/utils'; import type { CreateVisit, Visit } from '../types'; import type { LoadVisits, VisitsInfo, VisitsLoaded } from './types'; import { createNewVisits } from './visitCreation'; diff --git a/src/shlink-web-component/visits/reducers/domainVisits.ts b/src/shlink-web-component/visits/reducers/domainVisits.ts index ec67e4cf..15a88556 100644 --- a/src/shlink-web-component/visits/reducers/domainVisits.ts +++ b/src/shlink-web-component/visits/reducers/domainVisits.ts @@ -1,5 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; import { isBetween } from '../../../utils/helpers/date'; +import type { ShlinkApiClient } from '../../api-contract'; import { domainMatches } from '../../short-urls/helpers'; import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common'; import type { LoadVisits, VisitsInfo } from './types'; diff --git a/src/shlink-web-component/visits/reducers/nonOrphanVisits.ts b/src/shlink-web-component/visits/reducers/nonOrphanVisits.ts index 7555a5fc..600df155 100644 --- a/src/shlink-web-component/visits/reducers/nonOrphanVisits.ts +++ b/src/shlink-web-component/visits/reducers/nonOrphanVisits.ts @@ -1,5 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; import { isBetween } from '../../../utils/helpers/date'; +import type { ShlinkApiClient } from '../../api-contract'; import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common'; import type { VisitsInfo } from './types'; diff --git a/src/shlink-web-component/visits/reducers/orphanVisits.ts b/src/shlink-web-component/visits/reducers/orphanVisits.ts index 82cf869a..813b7279 100644 --- a/src/shlink-web-component/visits/reducers/orphanVisits.ts +++ b/src/shlink-web-component/visits/reducers/orphanVisits.ts @@ -1,6 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; -import type { ShlinkApiClientBuilder } from '../../../api/services/ShlinkApiClientBuilder'; import { isBetween } from '../../../utils/helpers/date'; +import type { ShlinkApiClient } from '../../api-contract'; import type { OrphanVisit, OrphanVisitType } from '../types'; import { isOrphanVisit } from '../types/helpers'; import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common'; diff --git a/src/shlink-web-component/visits/reducers/shortUrlVisits.ts b/src/shlink-web-component/visits/reducers/shortUrlVisits.ts index 390da505..17ca1638 100644 --- a/src/shlink-web-component/visits/reducers/shortUrlVisits.ts +++ b/src/shlink-web-component/visits/reducers/shortUrlVisits.ts @@ -1,5 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; import { isBetween } from '../../../utils/helpers/date'; +import type { ShlinkApiClient } from '../../api-contract'; import type { ShortUrlIdentifier } from '../../short-urls/data'; import { shortUrlMatches } from '../../short-urls/helpers'; import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common'; diff --git a/src/shlink-web-component/visits/reducers/tagVisits.ts b/src/shlink-web-component/visits/reducers/tagVisits.ts index e2e1b7c4..068e6cb4 100644 --- a/src/shlink-web-component/visits/reducers/tagVisits.ts +++ b/src/shlink-web-component/visits/reducers/tagVisits.ts @@ -1,5 +1,5 @@ -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; import { isBetween } from '../../../utils/helpers/date'; +import type { ShlinkApiClient } from '../../api-contract'; import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common'; import type { LoadVisits, VisitsInfo } from './types'; diff --git a/src/shlink-web-component/visits/reducers/types/index.ts b/src/shlink-web-component/visits/reducers/types/index.ts index a2af0e50..b27dec4e 100644 --- a/src/shlink-web-component/visits/reducers/types/index.ts +++ b/src/shlink-web-component/visits/reducers/types/index.ts @@ -1,6 +1,5 @@ -import type { ShlinkVisitsParams } from '../../../../api/types'; -import type { ProblemDetailsError } from '../../../../api/types/errors'; import type { DateInterval } from '../../../../utils/helpers/dateIntervals'; +import type { ProblemDetailsError, ShlinkVisitsParams } from '../../../api-contract'; import type { Visit } from '../../types'; export interface VisitsInfo { diff --git a/src/shlink-web-component/visits/reducers/visitsOverview.ts b/src/shlink-web-component/visits/reducers/visitsOverview.ts index ec71568a..627759a6 100644 --- a/src/shlink-web-component/visits/reducers/visitsOverview.ts +++ b/src/shlink-web-component/visits/reducers/visitsOverview.ts @@ -1,8 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; -import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient'; -import type { ShlinkVisitsOverview } from '../../../api/types'; import { createAsyncThunk } from '../../../utils/helpers/redux'; +import type { ShlinkApiClient, ShlinkVisitsOverview } from '../../api-contract'; import type { CreateVisit } from '../types'; import { groupNewVisitsByType } from '../types/helpers'; import { createNewVisits } from './visitCreation';