mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-09 09:47:28 +03:00
Define Shlink API contract
This commit is contained in:
parent
3fe48779be
commit
5f6dc186e3
42 changed files with 161 additions and 126 deletions
|
@ -1,5 +1,5 @@
|
||||||
import type { ProblemDetailsError } from './types/errors';
|
import type { ProblemDetailsError } from '../shlink-web-component/api-contract';
|
||||||
import { isInvalidArgumentError } from './utils';
|
import { isInvalidArgumentError } from '../shlink-web-component/api-contract/utils';
|
||||||
|
|
||||||
export interface ShlinkApiErrorProps {
|
export interface ShlinkApiErrorProps {
|
||||||
errorData?: ProblemDetailsError;
|
errorData?: ProblemDetailsError;
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { isEmpty, isNil, reject } from 'ramda';
|
import { isEmpty, isNil, reject } from 'ramda';
|
||||||
import type { HttpClient } from '../../common/services/HttpClient';
|
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 {
|
import type {
|
||||||
|
ShlinkApiClient as BaseShlinkApiClient,
|
||||||
ShlinkDomainRedirects,
|
ShlinkDomainRedirects,
|
||||||
ShlinkDomainsResponse,
|
ShlinkDomainsResponse,
|
||||||
ShlinkEditDomainRedirects,
|
ShlinkEditDomainRedirects,
|
||||||
|
@ -20,9 +16,13 @@ import type {
|
||||||
ShlinkTagsStatsResponse,
|
ShlinkTagsStatsResponse,
|
||||||
ShlinkVisits,
|
ShlinkVisits,
|
||||||
ShlinkVisitsOverview,
|
ShlinkVisitsOverview,
|
||||||
ShlinkVisitsParams,
|
ShlinkVisitsParams } from '../../shlink-web-component/api-contract';
|
||||||
} from '../types';
|
import { isRegularNotFound, parseApiError } from '../../shlink-web-component/api-contract/utils';
|
||||||
import { isRegularNotFound, parseApiError } from '../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;
|
type ApiVersion = 2 | 3;
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ const normalizeListParams = (
|
||||||
orderBy: orderToString(orderBy),
|
orderBy: orderToString(orderBy),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class ShlinkApiClient {
|
export class ShlinkApiClient implements BaseShlinkApiClient {
|
||||||
private apiVersion: ApiVersion;
|
private apiVersion: ApiVersion;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface MenuLayoutProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME Rename this to something else
|
||||||
export const MenuLayout = (
|
export const MenuLayout = (
|
||||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||||
ServerError: FC,
|
ServerError: FC,
|
||||||
|
|
|
@ -8,10 +8,7 @@ import { AsideMenu } from '../common/AsideMenu';
|
||||||
import { NotFound } from '../common/NotFound';
|
import { NotFound } from '../common/NotFound';
|
||||||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||||
import { useFeature } from './utils/features';
|
import { useFeature } from './utils/features';
|
||||||
|
import { useRoutesPrefix } from './utils/routesPrefix';
|
||||||
type MainProps = {
|
|
||||||
routesPrefix?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Main = (
|
export const Main = (
|
||||||
TagsList: FC,
|
TagsList: FC,
|
||||||
|
@ -25,8 +22,9 @@ export const Main = (
|
||||||
Overview: FC,
|
Overview: FC,
|
||||||
EditShortUrl: FC,
|
EditShortUrl: FC,
|
||||||
ManageDomains: FC,
|
ManageDomains: FC,
|
||||||
): FC<MainProps> => ({ routesPrefix = '' }) => {
|
): FC => () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const routesPrefix = useRoutesPrefix();
|
||||||
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
|
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
|
||||||
useEffect(() => hideSidebar(), [location]);
|
useEffect(() => hideSidebar(), [location]);
|
||||||
|
|
||||||
|
@ -71,5 +69,3 @@ export const Main = (
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MainType = ReturnType<typeof Main>;
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import type { FC, ReactNode } from 'react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import type { SemVer } from '../utils/helpers/version';
|
import type { SemVer } from '../utils/helpers/version';
|
||||||
|
import type { ShlinkApiClient } from './api-contract';
|
||||||
import { setUpStore } from './container/store';
|
import { setUpStore } from './container/store';
|
||||||
import { FeaturesProvider, useFeatures } from './utils/features';
|
import { FeaturesProvider, useFeatures } from './utils/features';
|
||||||
|
import { RoutesPrefixProvider } from './utils/routesPrefix';
|
||||||
import type { Settings } from './utils/settings';
|
import type { Settings } from './utils/settings';
|
||||||
import { SettingsProvider } from './utils/settings';
|
import { SettingsProvider } from './utils/settings';
|
||||||
|
|
||||||
|
@ -13,7 +15,7 @@ type ShlinkWebComponentProps = {
|
||||||
routesPrefix?: string;
|
routesPrefix?: string;
|
||||||
settings?: Settings;
|
settings?: Settings;
|
||||||
serverVersion: SemVer;
|
serverVersion: SemVer;
|
||||||
apiClient: any;
|
apiClient: ShlinkApiClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createShlinkWebComponent = (
|
export const createShlinkWebComponent = (
|
||||||
|
@ -30,7 +32,7 @@ export const createShlinkWebComponent = (
|
||||||
// depend on it
|
// depend on it
|
||||||
const { container } = bottle;
|
const { container } = bottle;
|
||||||
const { Main } = container;
|
const { Main } = container;
|
||||||
mainContent.current = <Main routesPrefix={routesPrefix} />;
|
mainContent.current = <Main />;
|
||||||
setStore(setUpStore(container));
|
setStore(setUpStore(container));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -38,7 +40,9 @@ export const createShlinkWebComponent = (
|
||||||
<Provider store={theStore}>
|
<Provider store={theStore}>
|
||||||
<SettingsProvider value={settings}>
|
<SettingsProvider value={settings}>
|
||||||
<FeaturesProvider value={features}>
|
<FeaturesProvider value={features}>
|
||||||
{mainContent.current}
|
<RoutesPrefixProvider value={routesPrefix}>
|
||||||
|
{mainContent.current}
|
||||||
|
</RoutesPrefixProvider>
|
||||||
</FeaturesProvider>
|
</FeaturesProvider>
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
62
src/shlink-web-component/api-contract/ShlinkApiClient.ts
Normal file
62
src/shlink-web-component/api-contract/ShlinkApiClient.ts
Normal file
|
@ -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<ShlinkShortUrlsResponse>;
|
||||||
|
|
||||||
|
createShortUrl(options: ShortUrlData): Promise<ShortUrl>;
|
||||||
|
|
||||||
|
getShortUrlVisits(shortCode: string, query?: ShlinkVisitsParams): Promise<ShlinkVisits>;
|
||||||
|
|
||||||
|
getTagVisits(tag: string, query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;
|
||||||
|
|
||||||
|
getDomainVisits(domain: string, query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;
|
||||||
|
|
||||||
|
getOrphanVisits(query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;
|
||||||
|
|
||||||
|
getNonOrphanVisits(query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits>;
|
||||||
|
|
||||||
|
getVisitsOverview(): Promise<ShlinkVisitsOverview>;
|
||||||
|
|
||||||
|
getShortUrl(shortCode: string, domain?: string | null): Promise<ShortUrl>;
|
||||||
|
|
||||||
|
deleteShortUrl(shortCode: string, domain?: string | null): Promise<void>;
|
||||||
|
|
||||||
|
updateShortUrl(
|
||||||
|
shortCode: string,
|
||||||
|
domain: string | null | undefined,
|
||||||
|
body: ShlinkShortUrlData,
|
||||||
|
): Promise<ShortUrl>;
|
||||||
|
|
||||||
|
listTags(): Promise<ShlinkTags>;
|
||||||
|
|
||||||
|
tagsStats(): Promise<ShlinkTags>;
|
||||||
|
|
||||||
|
deleteTags(tags: string[]): Promise<{ tags: string[] }>;
|
||||||
|
|
||||||
|
editTag(oldName: string, newName: string): Promise<{ oldName: string; newName: string }>;
|
||||||
|
|
||||||
|
health(authority?: string): Promise<ShlinkHealth>;
|
||||||
|
|
||||||
|
mercureInfo(): Promise<ShlinkMercureInfo>;
|
||||||
|
|
||||||
|
listDomains(): Promise<ShlinkDomainsResponse>;
|
||||||
|
|
||||||
|
editDomainRedirects(domainRedirects: ShlinkEditDomainRedirects): Promise<ShlinkDomainRedirects>;
|
||||||
|
};
|
3
src/shlink-web-component/api-contract/index.ts
Normal file
3
src/shlink-web-component/api-contract/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './errors';
|
||||||
|
export * from './ShlinkApiClient';
|
||||||
|
export * from './types';
|
|
@ -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 { Order } from '../../utils/helpers/ordering';
|
||||||
import type { OptionalString } from '../../utils/utils';
|
import type { OptionalString } from '../../utils/utils';
|
||||||
|
import type { ShortUrl, ShortUrlMeta } from '../short-urls/data';
|
||||||
|
import type { Visit } from '../visits/types';
|
||||||
|
|
||||||
export interface ShlinkShortUrlsResponse {
|
export interface ShlinkShortUrlsResponse {
|
||||||
data: ShortUrl[];
|
data: ShortUrl[];
|
|
@ -2,16 +2,11 @@ import type {
|
||||||
InvalidArgumentError,
|
InvalidArgumentError,
|
||||||
InvalidShortUrlDeletion,
|
InvalidShortUrlDeletion,
|
||||||
ProblemDetailsError,
|
ProblemDetailsError,
|
||||||
RegularNotFound } from '../types/errors';
|
RegularNotFound } from './errors';
|
||||||
import {
|
import {
|
||||||
ErrorTypeV2,
|
ErrorTypeV2,
|
||||||
ErrorTypeV3,
|
ErrorTypeV3,
|
||||||
} from '../types/errors';
|
} from './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);
|
|
||||||
|
|
||||||
export const isInvalidArgumentError = (error?: ProblemDetailsError): error is InvalidArgumentError =>
|
export const isInvalidArgumentError = (error?: ProblemDetailsError): error is InvalidArgumentError =>
|
||||||
error?.type === ErrorTypeV2.INVALID_ARGUMENT || error?.type === ErrorTypeV3.INVALID_ARGUMENT;
|
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 =>
|
export const isRegularNotFound = (error?: ProblemDetailsError): error is RegularNotFound =>
|
||||||
(error?.type === ErrorTypeV2.NOT_FOUND || error?.type === ErrorTypeV3.NOT_FOUND) && error?.status === 404;
|
(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);
|
|
@ -1,6 +1,5 @@
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import type { ShlinkDomainRedirects } from '../../../api/types';
|
|
||||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ShlinkApiClient, ShlinkDomainRedirects } from '../../api-contract';
|
||||||
|
|
||||||
const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS';
|
const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS';
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import type { AsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { AsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { createAction, createSlice } 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 { 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 { Domain, DomainStatus } from '../data';
|
||||||
import type { EditDomainRedirects } from './domainRedirects';
|
import type { EditDomainRedirects } from './domainRedirects';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ShlinkApiClient, ShlinkMercureInfo } from '../../api-contract';
|
||||||
|
|
||||||
const REDUCER_PREFIX = 'shlink/mercure';
|
const REDUCER_PREFIX = 'shlink/mercure';
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,6 @@ export const Overview = (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<ShortUrlsTable
|
<ShortUrlsTable
|
||||||
shortUrlsList={shortUrlsList}
|
shortUrlsList={shortUrlsList}
|
||||||
selectedServer={selectedServer}
|
|
||||||
className="mb-0"
|
className="mb-0"
|
||||||
onTagClick={(tag) => navigate(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)}
|
onTagClick={(tag) => navigate(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
|
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
|
||||||
import type { ShlinkPaginator } from '../../api/types';
|
|
||||||
import type {
|
import type {
|
||||||
NumberOrEllipsis } from '../../utils/helpers/pagination';
|
NumberOrEllipsis } from '../../utils/helpers/pagination';
|
||||||
import {
|
import {
|
||||||
|
@ -9,17 +8,19 @@ import {
|
||||||
prettifyPageNumber,
|
prettifyPageNumber,
|
||||||
progressivePagination,
|
progressivePagination,
|
||||||
} from '../../utils/helpers/pagination';
|
} from '../../utils/helpers/pagination';
|
||||||
|
import type { ShlinkPaginator } from '../api-contract';
|
||||||
|
import { useRoutesPrefix } from '../utils/routesPrefix';
|
||||||
|
|
||||||
interface PaginatorProps {
|
interface PaginatorProps {
|
||||||
paginator?: ShlinkPaginator;
|
paginator?: ShlinkPaginator;
|
||||||
serverId: string;
|
|
||||||
currentQueryString?: string;
|
currentQueryString?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Paginator = ({ paginator, serverId, currentQueryString = '' }: PaginatorProps) => {
|
export const Paginator = ({ paginator, currentQueryString = '' }: PaginatorProps) => {
|
||||||
const { currentPage = 0, pagesCount = 0 } = paginator ?? {};
|
const { currentPage = 0, pagesCount = 0 } = paginator ?? {};
|
||||||
|
const routesPrefix = useRoutesPrefix();
|
||||||
const urlForPage = (pageNumber: NumberOrEllipsis) =>
|
const urlForPage = (pageNumber: NumberOrEllipsis) =>
|
||||||
`/server/${serverId}/list-short-urls/${pageNumber}${currentQueryString}`;
|
`${routesPrefix}/list-short-urls/${pageNumber}${currentQueryString}`;
|
||||||
|
|
||||||
if (pagesCount <= 1) {
|
if (pagesCount <= 1) {
|
||||||
return <div className="pb-3" />; // Return some space
|
return <div className="pb-3" />; // Return some space
|
||||||
|
|
|
@ -2,13 +2,11 @@ import { pipe } from 'ramda';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { Card } from 'reactstrap';
|
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 { DEFAULT_SHORT_URLS_ORDERING } from '../../settings/reducers/settings';
|
||||||
import type { OrderDir } from '../../utils/helpers/ordering';
|
import type { OrderDir } from '../../utils/helpers/ordering';
|
||||||
import { determineOrderDir } from '../../utils/helpers/ordering';
|
import { determineOrderDir } from '../../utils/helpers/ordering';
|
||||||
import { TableOrderIcon } from '../../utils/table/TableOrderIcon';
|
import { TableOrderIcon } from '../../utils/table/TableOrderIcon';
|
||||||
|
import type { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../api-contract';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { useFeature } from '../utils/features';
|
import { useFeature } from '../utils/features';
|
||||||
|
@ -21,7 +19,6 @@ import type { ShortUrlsFilteringBarType } from './ShortUrlsFilteringBar';
|
||||||
import type { ShortUrlsTableType } from './ShortUrlsTable';
|
import type { ShortUrlsTableType } from './ShortUrlsTable';
|
||||||
|
|
||||||
interface ShortUrlsListProps {
|
interface ShortUrlsListProps {
|
||||||
selectedServer: SelectedServer;
|
|
||||||
shortUrlsList: ShortUrlsListState;
|
shortUrlsList: ShortUrlsListState;
|
||||||
listShortUrls: (params: ShlinkShortUrlsListParams) => void;
|
listShortUrls: (params: ShlinkShortUrlsListParams) => void;
|
||||||
}
|
}
|
||||||
|
@ -29,8 +26,7 @@ interface ShortUrlsListProps {
|
||||||
export const ShortUrlsList = (
|
export const ShortUrlsList = (
|
||||||
ShortUrlsTable: ShortUrlsTableType,
|
ShortUrlsTable: ShortUrlsTableType,
|
||||||
ShortUrlsFilteringBar: ShortUrlsFilteringBarType,
|
ShortUrlsFilteringBar: ShortUrlsFilteringBarType,
|
||||||
) => boundToMercureHub(({ listShortUrls, shortUrlsList, selectedServer }: ShortUrlsListProps) => {
|
) => boundToMercureHub(({ listShortUrls, shortUrlsList }: ShortUrlsListProps) => {
|
||||||
const serverId = getServerId(selectedServer);
|
|
||||||
const { page } = useParams();
|
const { page } = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [filter, toFirstPage] = useShortUrlsQuery();
|
const [filter, toFirstPage] = useShortUrlsQuery();
|
||||||
|
@ -108,13 +104,12 @@ export const ShortUrlsList = (
|
||||||
/>
|
/>
|
||||||
<Card body className="pb-0">
|
<Card body className="pb-0">
|
||||||
<ShortUrlsTable
|
<ShortUrlsTable
|
||||||
selectedServer={selectedServer}
|
|
||||||
shortUrlsList={shortUrlsList}
|
shortUrlsList={shortUrlsList}
|
||||||
orderByColumn={orderByColumn}
|
orderByColumn={orderByColumn}
|
||||||
renderOrderIcon={renderOrderIcon}
|
renderOrderIcon={renderOrderIcon}
|
||||||
onTagClick={addTag}
|
onTagClick={addTag}
|
||||||
/>
|
/>
|
||||||
<Paginator paginator={pagination} serverId={serverId} currentQueryString={location.search} />
|
<Paginator paginator={pagination} currentQueryString={location.search} />
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isEmpty } from 'ramda';
|
import { isEmpty } from 'ramda';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { SelectedServer } from '../../servers/data';
|
|
||||||
import type { ShortUrlsOrderableFields } from './data';
|
import type { ShortUrlsOrderableFields } from './data';
|
||||||
import type { ShortUrlsRowType } from './helpers/ShortUrlsRow';
|
import type { ShortUrlsRowType } from './helpers/ShortUrlsRow';
|
||||||
import type { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
import type { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
||||||
|
@ -11,7 +10,6 @@ interface ShortUrlsTableProps {
|
||||||
orderByColumn?: (column: ShortUrlsOrderableFields) => () => void;
|
orderByColumn?: (column: ShortUrlsOrderableFields) => () => void;
|
||||||
renderOrderIcon?: (column: ShortUrlsOrderableFields) => ReactNode;
|
renderOrderIcon?: (column: ShortUrlsOrderableFields) => ReactNode;
|
||||||
shortUrlsList: ShortUrlsListState;
|
shortUrlsList: ShortUrlsListState;
|
||||||
selectedServer: SelectedServer;
|
|
||||||
onTagClick?: (tag: string) => void;
|
onTagClick?: (tag: string) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +19,6 @@ export const ShortUrlsTable = (ShortUrlsRow: ShortUrlsRowType) => ({
|
||||||
renderOrderIcon,
|
renderOrderIcon,
|
||||||
shortUrlsList,
|
shortUrlsList,
|
||||||
onTagClick,
|
onTagClick,
|
||||||
selectedServer,
|
|
||||||
className,
|
className,
|
||||||
}: ShortUrlsTableProps) => {
|
}: ShortUrlsTableProps) => {
|
||||||
const { error, loading, shortUrls } = shortUrlsList;
|
const { error, loading, shortUrls } = shortUrlsList;
|
||||||
|
@ -52,7 +49,6 @@ export const ShortUrlsTable = (ShortUrlsRow: ShortUrlsRowType) => ({
|
||||||
<ShortUrlsRow
|
<ShortUrlsRow
|
||||||
key={shortUrl.shortUrl}
|
key={shortUrl.shortUrl}
|
||||||
shortUrl={shortUrl}
|
shortUrl={shortUrl}
|
||||||
selectedServer={selectedServer}
|
|
||||||
onTagClick={onTagClick}
|
onTagClick={onTagClick}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { pipe } from 'ramda';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||||
import { ShlinkApiError } from '../../../api/ShlinkApiError';
|
import { ShlinkApiError } from '../../../api/ShlinkApiError';
|
||||||
import { isInvalidDeletionError } from '../../../api/utils';
|
|
||||||
import { Result } from '../../../utils/Result';
|
import { Result } from '../../../utils/Result';
|
||||||
import { handleEventPreventingDefault } from '../../../utils/utils';
|
import { handleEventPreventingDefault } from '../../../utils/utils';
|
||||||
|
import { isInvalidDeletionError } from '../../api-contract/utils';
|
||||||
import type { ShortUrlIdentifier, ShortUrlModalProps } from '../data';
|
import type { ShortUrlIdentifier, ShortUrlModalProps } from '../data';
|
||||||
import type { ShortUrlDeletion } from '../reducers/shortUrlDeletion';
|
import type { ShortUrlDeletion } from '../reducers/shortUrlDeletion';
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import type { ReportExporter } from '../../../common/services/ReportExporter';
|
import type { ReportExporter } from '../../../common/services/ReportExporter';
|
||||||
import type { SelectedServer } from '../../../servers/data';
|
import type { SelectedServer } from '../../../servers/data';
|
||||||
import { isServerWithId } from '../../../servers/data';
|
import { isServerWithId } from '../../../servers/data';
|
||||||
import { ExportBtn } from '../../../utils/ExportBtn';
|
import { ExportBtn } from '../../../utils/ExportBtn';
|
||||||
import { useToggle } from '../../../utils/helpers/hooks';
|
import { useToggle } from '../../../utils/helpers/hooks';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import type { ShortUrl } from '../data';
|
import type { ShortUrl } from '../data';
|
||||||
import { useShortUrlsQuery } from './hooks';
|
import { useShortUrlsQuery } from './hooks';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import type { SelectedServer, ServerWithId } from '../../../servers/data';
|
import { useRoutesPrefix } from '../../utils/routesPrefix';
|
||||||
import { isServerWithId } from '../../../servers/data';
|
|
||||||
import type { ShortUrl } from '../data';
|
import type { ShortUrl } from '../data';
|
||||||
import { urlEncodeShortCode } from './index';
|
import { urlEncodeShortCode } from './index';
|
||||||
|
|
||||||
|
@ -9,21 +8,22 @@ export type LinkSuffix = 'visits' | 'edit';
|
||||||
|
|
||||||
export interface ShortUrlDetailLinkProps {
|
export interface ShortUrlDetailLinkProps {
|
||||||
shortUrl?: ShortUrl | null;
|
shortUrl?: ShortUrl | null;
|
||||||
selectedServer?: SelectedServer;
|
|
||||||
suffix: LinkSuffix;
|
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}` : '';
|
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<ShortUrlDetailLinkProps & Record<string | number, any>> = (
|
export const ShortUrlDetailLink: FC<ShortUrlDetailLinkProps & Record<string | number, any>> = (
|
||||||
{ selectedServer, shortUrl, suffix, children, ...rest },
|
{ shortUrl, suffix, asLink, children, ...rest },
|
||||||
) => {
|
) => {
|
||||||
if (!selectedServer || !isServerWithId(selectedServer) || !shortUrl) {
|
const routePrefix = useRoutesPrefix();
|
||||||
|
if (!asLink || !shortUrl) {
|
||||||
return <span {...rest}>{children}</span>;
|
return <span {...rest}>{children}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Link to={buildUrl(selectedServer, shortUrl, suffix)} {...rest}>{children}</Link>;
|
return <Link to={buildUrl(routePrefix, shortUrl, suffix)} {...rest}>{children}</Link>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import type { SelectedServer } from '../../../servers/data';
|
|
||||||
import { formatHumanFriendly, parseISO } from '../../../utils/helpers/date';
|
import { formatHumanFriendly, parseISO } from '../../../utils/helpers/date';
|
||||||
import { useElementRef } from '../../../utils/helpers/hooks';
|
import { useElementRef } from '../../../utils/helpers/hooks';
|
||||||
import { prettify } from '../../../utils/helpers/numbers';
|
import { prettify } from '../../../utils/helpers/numbers';
|
||||||
|
@ -12,18 +11,18 @@ import './ShortUrlVisitsCount.scss';
|
||||||
|
|
||||||
interface ShortUrlVisitsCountProps {
|
interface ShortUrlVisitsCountProps {
|
||||||
shortUrl?: ShortUrl | null;
|
shortUrl?: ShortUrl | null;
|
||||||
selectedServer?: SelectedServer;
|
|
||||||
visitsCount: number;
|
visitsCount: number;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
asLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShortUrlVisitsCount = (
|
export const ShortUrlVisitsCount = (
|
||||||
{ visitsCount, shortUrl, selectedServer, active = false }: ShortUrlVisitsCountProps,
|
{ visitsCount, shortUrl, active = false, asLink = false }: ShortUrlVisitsCountProps,
|
||||||
) => {
|
) => {
|
||||||
const { maxVisits, validSince, validUntil } = shortUrl?.meta ?? {};
|
const { maxVisits, validSince, validUntil } = shortUrl?.meta ?? {};
|
||||||
const hasLimit = !!maxVisits || !!validSince || !!validUntil;
|
const hasLimit = !!maxVisits || !!validSince || !!validUntil;
|
||||||
const visitsLink = (
|
const visitsLink = (
|
||||||
<ShortUrlDetailLink selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
<ShortUrlDetailLink shortUrl={shortUrl} suffix="visits" asLink={asLink}>
|
||||||
<strong
|
<strong
|
||||||
className={classNames('short-url-visits-count__amount', { 'short-url-visits-count__amount--big': active })}
|
className={classNames('short-url-visits-count__amount', { 'short-url-visits-count__amount--big': active })}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import type { SelectedServer } from '../../../servers/data';
|
|
||||||
import { CopyToClipboardIcon } from '../../../utils/CopyToClipboardIcon';
|
import { CopyToClipboardIcon } from '../../../utils/CopyToClipboardIcon';
|
||||||
import { Time } from '../../../utils/dates/Time';
|
import { Time } from '../../../utils/dates/Time';
|
||||||
import type { TimeoutToggle } from '../../../utils/helpers/hooks';
|
import type { TimeoutToggle } from '../../../utils/helpers/hooks';
|
||||||
|
@ -17,7 +16,6 @@ import './ShortUrlsRow.scss';
|
||||||
|
|
||||||
interface ShortUrlsRowProps {
|
interface ShortUrlsRowProps {
|
||||||
onTagClick?: (tag: string) => void;
|
onTagClick?: (tag: string) => void;
|
||||||
selectedServer: SelectedServer;
|
|
||||||
shortUrl: ShortUrl;
|
shortUrl: ShortUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +25,7 @@ export const ShortUrlsRow = (
|
||||||
ShortUrlsRowMenu: ShortUrlsRowMenuType,
|
ShortUrlsRowMenu: ShortUrlsRowMenuType,
|
||||||
colorGenerator: ColorGenerator,
|
colorGenerator: ColorGenerator,
|
||||||
useTimeoutToggle: TimeoutToggle,
|
useTimeoutToggle: TimeoutToggle,
|
||||||
) => ({ shortUrl, selectedServer, onTagClick }: ShortUrlsRowProps) => {
|
) => ({ shortUrl, onTagClick }: ShortUrlsRowProps) => {
|
||||||
const [copiedToClipboard, setCopiedToClipboard] = useTimeoutToggle();
|
const [copiedToClipboard, setCopiedToClipboard] = useTimeoutToggle();
|
||||||
const [active, setActive] = useTimeoutToggle(false, 500);
|
const [active, setActive] = useTimeoutToggle(false, 500);
|
||||||
const isFirstRun = useRef(true);
|
const isFirstRun = useRef(true);
|
||||||
|
@ -76,15 +74,15 @@ export const ShortUrlsRow = (
|
||||||
doExcludeBots ? shortUrl.visitsSummary?.nonBots : shortUrl.visitsSummary?.total
|
doExcludeBots ? shortUrl.visitsSummary?.nonBots : shortUrl.visitsSummary?.total
|
||||||
) ?? shortUrl.visitsCount}
|
) ?? shortUrl.visitsCount}
|
||||||
shortUrl={shortUrl}
|
shortUrl={shortUrl}
|
||||||
selectedServer={selectedServer}
|
|
||||||
active={active}
|
active={active}
|
||||||
|
asLink
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="responsive-table__cell short-urls-row__cell" data-th="Status">
|
<td className="responsive-table__cell short-urls-row__cell" data-th="Status">
|
||||||
<ShortUrlStatus shortUrl={shortUrl} />
|
<ShortUrlStatus shortUrl={shortUrl} />
|
||||||
</td>
|
</td>
|
||||||
<td className="responsive-table__cell short-urls-row__cell text-end">
|
<td className="responsive-table__cell short-urls-row__cell text-end">
|
||||||
<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />
|
<ShortUrlsRowMenu shortUrl={shortUrl} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,14 +7,12 @@ import {
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import type { SelectedServer } from '../../../servers/data';
|
|
||||||
import { useToggle } from '../../../utils/helpers/hooks';
|
import { useToggle } from '../../../utils/helpers/hooks';
|
||||||
import { RowDropdownBtn } from '../../../utils/RowDropdownBtn';
|
import { RowDropdownBtn } from '../../../utils/RowDropdownBtn';
|
||||||
import type { ShortUrl, ShortUrlModalProps } from '../data';
|
import type { ShortUrl, ShortUrlModalProps } from '../data';
|
||||||
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
||||||
|
|
||||||
interface ShortUrlsRowMenuProps {
|
interface ShortUrlsRowMenuProps {
|
||||||
selectedServer: SelectedServer;
|
|
||||||
shortUrl: ShortUrl;
|
shortUrl: ShortUrl;
|
||||||
}
|
}
|
||||||
type ShortUrlModal = FC<ShortUrlModalProps>;
|
type ShortUrlModal = FC<ShortUrlModalProps>;
|
||||||
|
@ -22,17 +20,17 @@ type ShortUrlModal = FC<ShortUrlModalProps>;
|
||||||
export const ShortUrlsRowMenu = (
|
export const ShortUrlsRowMenu = (
|
||||||
DeleteShortUrlModal: ShortUrlModal,
|
DeleteShortUrlModal: ShortUrlModal,
|
||||||
QrCodeModal: ShortUrlModal,
|
QrCodeModal: ShortUrlModal,
|
||||||
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
|
) => ({ shortUrl }: ShortUrlsRowMenuProps) => {
|
||||||
const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle();
|
const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle();
|
||||||
const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle();
|
const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowDropdownBtn minWidth={190}>
|
<RowDropdownBtn minWidth={190}>
|
||||||
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
<DropdownItem tag={ShortUrlDetailLink} shortUrl={shortUrl} suffix="visits">
|
||||||
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
||||||
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="edit">
|
<DropdownItem tag={ShortUrlDetailLink} shortUrl={shortUrl} suffix="edit">
|
||||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit short URL
|
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit short URL
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } 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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract';
|
||||||
|
import { parseApiError } from '../../api-contract/utils';
|
||||||
import type { ShortUrl, ShortUrlData } from '../data';
|
import type { ShortUrl, ShortUrlData } from '../data';
|
||||||
|
|
||||||
const REDUCER_PREFIX = 'shlink/shortUrlCreation';
|
const REDUCER_PREFIX = 'shlink/shortUrlCreation';
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
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 { 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 type { ShortUrl, ShortUrlIdentifier } from '../data';
|
||||||
|
|
||||||
const REDUCER_PREFIX = 'shlink/shortUrlDeletion';
|
const REDUCER_PREFIX = 'shlink/shortUrlDeletion';
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } 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 { 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 type { ShortUrl, ShortUrlIdentifier } from '../data';
|
||||||
import { shortUrlMatches } from '../helpers';
|
import { shortUrlMatches } from '../helpers';
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } 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 { 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';
|
import type { EditShortUrlData, ShortUrl, ShortUrlIdentifier } from '../data';
|
||||||
|
|
||||||
const REDUCER_PREFIX = 'shlink/shortUrlEdition';
|
const REDUCER_PREFIX = 'shlink/shortUrlEdition';
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { assocPath, last, pipe, reject } from 'ramda';
|
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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ShlinkApiClient, ShlinkShortUrlsListParams, ShlinkShortUrlsResponse } from '../../api-contract';
|
||||||
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
||||||
import type { ShortUrl } from '../data';
|
import type { ShortUrl } from '../data';
|
||||||
import { shortUrlMatches } from '../helpers';
|
import { shortUrlMatches } from '../helpers';
|
||||||
|
@ -26,15 +25,7 @@ const initialState: ShortUrlsList = {
|
||||||
|
|
||||||
export const listShortUrls = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
export const listShortUrls = (apiClient: ShlinkApiClient) => createAsyncThunk(
|
||||||
`${REDUCER_PREFIX}/listShortUrls`,
|
`${REDUCER_PREFIX}/listShortUrls`,
|
||||||
(params: ShlinkShortUrlsListParams | void): Promise<ShlinkShortUrlsResponse> => {
|
(params: ShlinkShortUrlsListParams | void): Promise<ShlinkShortUrlsResponse> => apiClient.listShortUrls(params ?? {}),
|
||||||
try {
|
|
||||||
const { listShortUrls: shlinkListShortUrls } = apiClient;
|
|
||||||
return shlinkListShortUrls(params ?? {});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const shortUrlsListReducerCreator = (
|
export const shortUrlsListReducerCreator = (
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
// Components
|
// Components
|
||||||
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable', 'ShortUrlsFilteringBar');
|
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable', 'ShortUrlsFilteringBar');
|
||||||
bottle.decorator('ShortUrlsList', connect(
|
bottle.decorator('ShortUrlsList', connect(
|
||||||
['selectedServer', 'mercureInfo', 'shortUrlsList'],
|
['mercureInfo', 'shortUrlsList'],
|
||||||
['listShortUrls', 'createNewVisits', 'loadMercureInfo'],
|
['listShortUrls', 'createNewVisits', 'loadMercureInfo'],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ProblemDetailsError, ShlinkApiClient } from '../../api-contract';
|
||||||
|
import { parseApiError } from '../../api-contract/utils';
|
||||||
|
|
||||||
const REDUCER_PREFIX = 'shlink/tagDelete';
|
const REDUCER_PREFIX = 'shlink/tagDelete';
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import { pick } from 'ramda';
|
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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
import type { ColorGenerator } from '../../../utils/services/ColorGenerator';
|
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';
|
const REDUCER_PREFIX = 'shlink/tagEdit';
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import { isEmpty, reject } from 'ramda';
|
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 { isReachableServer } from '../../../servers/data';
|
||||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
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 type { createShortUrl } from '../../short-urls/reducers/shortUrlCreation';
|
||||||
import { isFeatureEnabledForVersion } from '../../utils/features';
|
import { isFeatureEnabledForVersion } from '../../utils/features';
|
||||||
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
||||||
|
|
7
src/shlink-web-component/utils/routesPrefix.ts
Normal file
7
src/shlink-web-component/utils/routesPrefix.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
const RoutesPrefixContext = createContext('');
|
||||||
|
|
||||||
|
export const RoutesPrefixProvider = RoutesPrefixContext.Provider;
|
||||||
|
|
||||||
|
export const useRoutesPrefix = (): string => useContext(RoutesPrefixContext);
|
|
@ -1,7 +1,7 @@
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import type { ShlinkVisitsParams } from '../../api/types';
|
|
||||||
import type { ReportExporter } from '../../common/services/ReportExporter';
|
import type { ReportExporter } from '../../common/services/ReportExporter';
|
||||||
import { useGoBack } from '../../utils/helpers/hooks';
|
import { useGoBack } from '../../utils/helpers/hooks';
|
||||||
|
import type { ShlinkVisitsParams } from '../api-contract';
|
||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import type { DomainVisits as DomainVisitsState, LoadDomainVisits } from './reducers/domainVisits';
|
import type { DomainVisits as DomainVisitsState, LoadDomainVisits } from './reducers/domainVisits';
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import { flatten, prop, range, splitEvery } from 'ramda';
|
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 { ShlinkState } from '../../../container/types';
|
||||||
import type { DateInterval } from '../../../utils/helpers/dateIntervals';
|
import type { DateInterval } from '../../../utils/helpers/dateIntervals';
|
||||||
import { dateToMatchingInterval } from '../../../utils/helpers/dateIntervals';
|
import { dateToMatchingInterval } from '../../../utils/helpers/dateIntervals';
|
||||||
import { createAsyncThunk } from '../../../utils/helpers/redux';
|
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 { CreateVisit, Visit } from '../types';
|
||||||
import type { LoadVisits, VisitsInfo, VisitsLoaded } from './types';
|
import type { LoadVisits, VisitsInfo, VisitsLoaded } from './types';
|
||||||
import { createNewVisits } from './visitCreation';
|
import { createNewVisits } from './visitCreation';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import { isBetween } from '../../../utils/helpers/date';
|
import { isBetween } from '../../../utils/helpers/date';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import { domainMatches } from '../../short-urls/helpers';
|
import { domainMatches } from '../../short-urls/helpers';
|
||||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||||
import type { LoadVisits, VisitsInfo } from './types';
|
import type { LoadVisits, VisitsInfo } from './types';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import { isBetween } from '../../../utils/helpers/date';
|
import { isBetween } from '../../../utils/helpers/date';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||||
import type { VisitsInfo } from './types';
|
import type { VisitsInfo } from './types';
|
||||||
|
|
||||||
|
|
|
@ -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 { isBetween } from '../../../utils/helpers/date';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import type { OrphanVisit, OrphanVisitType } from '../types';
|
import type { OrphanVisit, OrphanVisitType } from '../types';
|
||||||
import { isOrphanVisit } from '../types/helpers';
|
import { isOrphanVisit } from '../types/helpers';
|
||||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import { isBetween } from '../../../utils/helpers/date';
|
import { isBetween } from '../../../utils/helpers/date';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import type { ShortUrlIdentifier } from '../../short-urls/data';
|
import type { ShortUrlIdentifier } from '../../short-urls/data';
|
||||||
import { shortUrlMatches } from '../../short-urls/helpers';
|
import { shortUrlMatches } from '../../short-urls/helpers';
|
||||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ShlinkApiClient } from '../../../api/services/ShlinkApiClient';
|
|
||||||
import { isBetween } from '../../../utils/helpers/date';
|
import { isBetween } from '../../../utils/helpers/date';
|
||||||
|
import type { ShlinkApiClient } from '../../api-contract';
|
||||||
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
import { createVisitsAsyncThunk, createVisitsReducer, lastVisitLoaderForLoader } from './common';
|
||||||
import type { LoadVisits, VisitsInfo } from './types';
|
import type { LoadVisits, VisitsInfo } from './types';
|
||||||
|
|
||||||
|
|
|
@ -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 { DateInterval } from '../../../../utils/helpers/dateIntervals';
|
||||||
|
import type { ProblemDetailsError, ShlinkVisitsParams } from '../../../api-contract';
|
||||||
import type { Visit } from '../../types';
|
import type { Visit } from '../../types';
|
||||||
|
|
||||||
export interface VisitsInfo {
|
export interface VisitsInfo {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { createSlice } 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 { createAsyncThunk } from '../../../utils/helpers/redux';
|
||||||
|
import type { ShlinkApiClient, ShlinkVisitsOverview } from '../../api-contract';
|
||||||
import type { CreateVisit } from '../types';
|
import type { CreateVisit } from '../types';
|
||||||
import { groupNewVisitsByType } from '../types/helpers';
|
import { groupNewVisitsByType } from '../types/helpers';
|
||||||
import { createNewVisits } from './visitCreation';
|
import { createNewVisits } from './visitCreation';
|
||||||
|
|
Loading…
Reference in a new issue