Fix merge conflicts

This commit is contained in:
Alejandro Celaya 2023-03-08 09:21:05 +01:00
commit 3f1718f4c5
340 changed files with 3289 additions and 9498 deletions

View file

@ -1,5 +1,4 @@
./.github ./.github
./.stryker-tmp
./build ./build
./coverage ./coverage
./node_modules ./node_modules

View file

@ -12,6 +12,5 @@ jobs:
uses: shlinkio/github-actions/.github/workflows/web-app-ci.yml@main uses: shlinkio/github-actions/.github/workflows/web-app-ci.yml@main
with: with:
node-version: 18.12 node-version: 18.12
with-mutation-tests: true
publish-coverage: true publish-coverage: true
force-install: true force-install: true

1
.gitignore vendored
View file

@ -3,7 +3,6 @@
# testing # testing
/coverage /coverage
/.stryker-tmp
/reports /reports
# production # production

View file

@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased]
### Added
* *Nothing*
### Changed
* Update to Vite 4.1
* Update to coding standard v2.1.0
* [#798](https://github.com/shlinkio/shlink-web-client/issues/798) Remove stryker and mutation testing.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* *Nothing*
## [3.9.1] - 2022-12-31 ## [3.9.1] - 2022-12-31
### Added ### Added
* *Nothing* * *Nothing*

View file

@ -5,7 +5,7 @@
[![GitHub release](https://img.shields.io/github/release/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/releases/latest) [![GitHub release](https://img.shields.io/github/release/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/releases/latest)
[![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink-web-client.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink-web-client/) [![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink-web-client.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink-web-client/)
[![GitHub license](https://img.shields.io/github/license/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/blob/main/LICENSE) [![GitHub license](https://img.shields.io/github/license/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/blob/main/LICENSE)
[![Twitter](https://img.shields.io/twitter/follow/shlinkio?color=blue&label=follow&logo=twitter&style=flat-square)](https://twitter.com/shlinkio) [![Twitter](https://img.shields.io/badge/follow-shlinkio-blue.svg?style=flat-square&logo=twitter&color=blue)](https://twitter.com/shlinkio)
[![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio) [![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio)
[![Paypal Donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=cccccc)](https://slnk.to/donate) [![Paypal Donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=cccccc)](https://slnk.to/donate)
@ -54,7 +54,7 @@ Those servers can be exported and imported in other browsers, but if for some re
[ [
{ {
"name": "Main server", "name": "Main server",
"url": "https://doma.in", "url": "https://s.test",
"apiKey": "09c972b7-506b-49f1-a19a-d729e22e599c" "apiKey": "09c972b7-506b-49f1-a19a-d729e22e599c"
}, },
{ {
@ -85,7 +85,7 @@ If you want to pre-configure a single server, you can provide its config via env
docker run \ docker run \
--name shlink-web-client \ --name shlink-web-client \
-p 8000:80 \ -p 8000:80 \
-e SHLINK_SERVER_URL=https://doma.in \ -e SHLINK_SERVER_URL=https://s.test \
-e SHLINK_SERVER_API_KEY=6aeb82c6-e275-4538-a747-31f9abfba63c \ -e SHLINK_SERVER_API_KEY=6aeb82c6-e275-4538-a747-31f9abfba63c \
shlinkio/shlink-web-client shlinkio/shlink-web-client
``` ```

View file

@ -17,7 +17,6 @@ module.exports = {
}, },
setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.ts'], setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.ts'],
testMatch: ['<rootDir>/test/**/*.test.{ts,tsx}'], testMatch: ['<rootDir>/test/**/*.test.{ts,tsx}'],
modulePathIgnorePatterns: ['<rootDir>/.stryker-tmp'],
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
testEnvironmentOptions: { testEnvironmentOptions: {
url: 'http://localhost', url: 'http://localhost',
@ -28,7 +27,6 @@ module.exports = {
'^(?!.*\\.(ts|tsx|js|json|scss)$)': '<rootDir>/config/jest/fileTransform.js', '^(?!.*\\.(ts|tsx|js|json|scss)$)': '<rootDir>/config/jest/fileTransform.js',
}, },
transformIgnorePatterns: [ transformIgnorePatterns: [
'<rootDir>/.stryker-tmp',
'node_modules\/(?!(\@react-leaflet|react-leaflet|leaflet|react-chartjs-2|react-colorful)\/)', 'node_modules\/(?!(\@react-leaflet|react-leaflet|leaflet|react-chartjs-2|react-colorful)\/)',
'^.+\\.module\\.scss$', '^.+\\.module\\.scss$',
], ],

10012
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,8 +20,7 @@
"test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary", "test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary",
"test:ci": "npm run test:coverage -- --coverageReporters=clover --ci", "test:ci": "npm run test:coverage -- --coverageReporters=clover --ci",
"test:pretty": "npm run test:coverage -- --coverageReporters=html", "test:pretty": "npm run test:coverage -- --coverageReporters=html",
"test:verbose": "npm run test -- --verbose", "test:verbose": "npm run test -- --verbose"
"mutate": "./node_modules/.bin/stryker run --concurrency 4 --ignoreStatic"
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.20.2",
@ -71,11 +70,8 @@
"workbox-strategies": "^6.5.4" "workbox-strategies": "^6.5.4"
}, },
"devDependencies": { "devDependencies": {
"@shlinkio/eslint-config-js-coding-standard": "~2.0.2", "@shlinkio/eslint-config-js-coding-standard": "~2.1.0",
"@shlinkio/stylelint-config-css-coding-standard": "~1.0.1", "@shlinkio/stylelint-config-css-coding-standard": "~1.0.1",
"@stryker-mutator/core": "^6.3.1",
"@stryker-mutator/jest-runner": "^6.3.1",
"@stryker-mutator/typescript-checker": "^6.3.1",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
@ -91,7 +87,7 @@
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
"@types/react-tag-autocomplete": "^6.3.0", "@types/react-tag-autocomplete": "^6.3.0",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.1.0",
"adm-zip": "^0.5.10", "adm-zip": "^0.5.10",
"babel-jest": "^29.3.1", "babel-jest": "^29.3.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
@ -102,12 +98,11 @@
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.3.1",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sass": "^1.57.1", "sass": "^1.57.1",
"stryker-cli": "^1.0.2",
"stylelint": "^14.16.0", "stylelint": "^14.16.0",
"ts-mockery": "^1.2.0", "ts-mockery": "^1.2.0",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"vite": "^4.0.3", "vite": "^4.1.1",
"vite-plugin-pwa": "^0.14.0" "vite-plugin-pwa": "^0.14.1"
}, },
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",

View file

@ -1,5 +1,5 @@
import type { ProblemDetailsError } from './types/errors';
import { isInvalidArgumentError } from './utils'; import { isInvalidArgumentError } from './utils';
import { ProblemDetailsError } from './types/errors';
export interface ShlinkApiErrorProps { export interface ShlinkApiErrorProps {
errorData?: ProblemDetailsError; errorData?: ProblemDetailsError;

View file

@ -1,26 +1,26 @@
import { isEmpty, isNil, reject } from 'ramda'; import { isEmpty, isNil, reject } from 'ramda';
import { ShortUrl, ShortUrlData } from '../../short-urls/data'; import type { HttpClient } from '../../common/services/HttpClient';
import { OptionalString } from '../../utils/utils'; import type { ShortUrl, ShortUrlData } from '../../short-urls/data';
import { import { orderToString } from '../../utils/helpers/ordering';
import { stringifyQuery } from '../../utils/helpers/query';
import type { OptionalString } from '../../utils/utils';
import type {
ShlinkDomainRedirects,
ShlinkDomainsResponse,
ShlinkEditDomainRedirects,
ShlinkHealth, ShlinkHealth,
ShlinkMercureInfo, ShlinkMercureInfo,
ShlinkShortUrlData,
ShlinkShortUrlsListNormalizedParams,
ShlinkShortUrlsListParams,
ShlinkShortUrlsResponse, ShlinkShortUrlsResponse,
ShlinkTags, ShlinkTags,
ShlinkTagsResponse, ShlinkTagsResponse,
ShlinkVisits, ShlinkVisits,
ShlinkVisitsParams,
ShlinkShortUrlData,
ShlinkDomainsResponse,
ShlinkVisitsOverview, ShlinkVisitsOverview,
ShlinkEditDomainRedirects, ShlinkVisitsParams,
ShlinkDomainRedirects,
ShlinkShortUrlsListParams,
ShlinkShortUrlsListNormalizedParams,
} from '../types'; } from '../types';
import { orderToString } from '../../utils/helpers/ordering';
import { isRegularNotFound, parseApiError } from '../utils'; import { isRegularNotFound, parseApiError } from '../utils';
import { stringifyQuery } from '../../utils/helpers/query';
import { HttpClient } from '../../common/services/HttpClient';
const buildShlinkBaseUrl = (url: string, version: 2 | 3) => `${url}/rest/v${version}`; const buildShlinkBaseUrl = (url: string, version: 2 | 3) => `${url}/rest/v${version}`;
const rejectNilProps = reject(isNil); const rejectNilProps = reject(isNil);

View file

@ -1,7 +1,8 @@
import { hasServerData, ServerWithId } from '../../servers/data'; import type { HttpClient } from '../../common/services/HttpClient';
import { GetState } from '../../container/types'; import type { GetState } from '../../container/types';
import type { ServerWithId } from '../../servers/data';
import { hasServerData } from '../../servers/data';
import { ShlinkApiClient } from './ShlinkApiClient'; import { ShlinkApiClient } from './ShlinkApiClient';
import { HttpClient } from '../../common/services/HttpClient';
const apiClients: Record<string, ShlinkApiClient> = {}; const apiClients: Record<string, ShlinkApiClient> = {};

View file

@ -1,8 +1,6 @@
import Bottle from 'bottlejs'; import type Bottle from 'bottlejs';
import { buildShlinkApiClient } from './ShlinkApiClientBuilder'; import { buildShlinkApiClient } from './ShlinkApiClientBuilder';
const provideServices = (bottle: Bottle) => { export const provideServices = (bottle: Bottle) => {
bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'HttpClient'); bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'HttpClient');
}; };
export default provideServices;

View file

@ -1,7 +1,7 @@
import { Visit } from '../../visits/types'; import type { ShortUrl, ShortUrlMeta } from '../../short-urls/data';
import { OptionalString } from '../../utils/utils'; import type { Order } from '../../utils/helpers/ordering';
import { ShortUrl, ShortUrlMeta } from '../../short-urls/data'; import type { OptionalString } from '../../utils/utils';
import { Order } from '../../utils/helpers/ordering'; import type { Visit } from '../../visits/types';
export interface ShlinkShortUrlsResponse { export interface ShlinkShortUrlsResponse {
data: ShortUrl[]; data: ShortUrl[];

View file

@ -1,10 +1,11 @@
import { import type {
ErrorTypeV2,
ErrorTypeV3,
InvalidArgumentError, InvalidArgumentError,
InvalidShortUrlDeletion, InvalidShortUrlDeletion,
ProblemDetailsError, ProblemDetailsError,
RegularNotFound, RegularNotFound } from '../types/errors';
import {
ErrorTypeV2,
ErrorTypeV3,
} from '../types/errors'; } from '../types/errors';
const isProblemDetails = (e: unknown): e is ProblemDetailsError => const isProblemDetails = (e: unknown): e is ProblemDetailsError =>

View file

@ -1,12 +1,13 @@
import { useEffect, FC } from 'react';
import { Route, Routes, useLocation } from 'react-router-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { NotFound } from '../common/NotFound'; import type { FC } from 'react';
import { ServersMap } from '../servers/data'; import { useEffect } from 'react';
import { Settings } from '../settings/reducers/settings'; import { Route, Routes, useLocation } from 'react-router-dom';
import { changeThemeInMarkup } from '../utils/theme';
import { AppUpdateBanner } from '../common/AppUpdateBanner'; import { AppUpdateBanner } from '../common/AppUpdateBanner';
import { NotFound } from '../common/NotFound';
import type { ServersMap } from '../servers/data';
import type { Settings } from '../settings/reducers/settings';
import { forceUpdate } from '../utils/helpers/sw'; import { forceUpdate } from '../utils/helpers/sw';
import { changeThemeInMarkup } from '../utils/theme';
import './App.scss'; import './App.scss';
interface AppProps { interface AppProps {

View file

@ -1,9 +1,9 @@
import Bottle from 'bottlejs'; import type Bottle from 'bottlejs';
import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates'; import type { ConnectDecorator } from '../../container/types';
import { App } from '../App'; import { App } from '../App';
import { ConnectDecorator } from '../../container/types'; import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components // Components
bottle.serviceFactory( bottle.serviceFactory(
'App', 'App',
@ -23,5 +23,3 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable); bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable);
bottle.serviceFactory('resetAppUpdate', () => resetAppUpdate); bottle.serviceFactory('resetAppUpdate', () => resetAppUpdate);
}; };
export default provideServices;

View file

@ -1,9 +1,9 @@
import { FC, MouseEventHandler } from 'react';
import { Alert, Button } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSyncAlt as reloadIcon } from '@fortawesome/free-solid-svg-icons'; import { faSyncAlt as reloadIcon } from '@fortawesome/free-solid-svg-icons';
import { SimpleCard } from '../utils/SimpleCard'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC, MouseEventHandler } from 'react';
import { Alert, Button } from 'reactstrap';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { SimpleCard } from '../utils/SimpleCard';
import './AppUpdateBanner.scss'; import './AppUpdateBanner.scss';
interface AppUpdateBannerProps { interface AppUpdateBannerProps {

View file

@ -1,17 +1,19 @@
import { import {
faList as listIcon,
faLink as createIcon,
faTags as tagsIcon,
faPen as editIcon,
faHome as overviewIcon,
faGlobe as domainsIcon, faGlobe as domainsIcon,
faHome as overviewIcon,
faLink as createIcon,
faList as listIcon,
faPen as editIcon,
faTags as tagsIcon,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FC } from 'react';
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { DeleteServerButtonProps } from '../servers/DeleteServerButton'; import type { FC } from 'react';
import { isServerWithId, SelectedServer } from '../servers/data'; import type { NavLinkProps } from 'react-router-dom';
import { NavLink, useLocation } from 'react-router-dom';
import type { SelectedServer } from '../servers/data';
import { isServerWithId } from '../servers/data';
import type { DeleteServerButtonProps } from '../servers/DeleteServerButton';
import './AsideMenu.scss'; import './AsideMenu.scss';
export interface AsideMenuProps { export interface AsideMenuProps {

View file

@ -1,4 +1,5 @@
import { Component, ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Component } from 'react';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';

View file

@ -1,12 +1,12 @@
import { useEffect } from 'react'; import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, values } from 'ramda'; import { isEmpty, values } from 'ramda';
import { useEffect } from 'react';
import { ExternalLink } from 'react-external-link';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Card, Row } from 'reactstrap'; import { Card, Row } from 'reactstrap';
import { ExternalLink } from 'react-external-link'; import type { ServersMap } from '../servers/data';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
import { ServersListGroup } from '../servers/ServersListGroup'; import { ServersListGroup } from '../servers/ServersListGroup';
import { ServersMap } from '../servers/data';
import { ShlinkLogo } from './img/ShlinkLogo'; import { ShlinkLogo } from './img/ShlinkLogo';
import './Home.scss'; import './Home.scss';

View file

@ -1,9 +1,10 @@
import { faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; import { faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FC, useEffect } from 'react'; import classNames from 'classnames';
import type { FC } from 'react';
import { useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import classNames from 'classnames';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { ShlinkLogo } from './img/ShlinkLogo'; import { ShlinkLogo } from './img/ShlinkLogo';
import './MainHeader.scss'; import './MainHeader.scss';

View file

@ -1,14 +1,15 @@
import { FC, useEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; import { faBars as burgerIcon } 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 { withSelectedServer } from '../servers/helpers/withSelectedServer'; import type { FC } from 'react';
import { useSwipeable, useToggle } from '../utils/helpers/hooks'; import { useEffect } from 'react';
import { supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features';
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
import type { AsideMenuProps } from './AsideMenu';
import { NotFound } from './NotFound'; import { NotFound } from './NotFound';
import { AsideMenuProps } from './AsideMenu';
import './MenuLayout.scss'; import './MenuLayout.scss';
interface MenuLayoutProps { interface MenuLayoutProps {

View file

@ -1,4 +1,4 @@
import { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import './NoMenuLayout.scss'; import './NoMenuLayout.scss';
export const NoMenuLayout: FC<PropsWithChildren<unknown>> = ({ children }) => ( export const NoMenuLayout: FC<PropsWithChildren<unknown>> = ({ children }) => (

View file

@ -1,4 +1,4 @@
import { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';

View file

@ -1,4 +1,5 @@
import { FC, PropsWithChildren, useEffect } from 'react'; import type { FC, PropsWithChildren } from 'react';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
export const ScrollToTop: FC<PropsWithChildren<unknown>> = ({ children }) => { export const ScrollToTop: FC<PropsWithChildren<unknown>> = ({ children }) => {

View file

@ -1,7 +1,8 @@
import { pipe } from 'ramda'; import { pipe } from 'ramda';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import type { SelectedServer } from '../servers/data';
import { isReachableServer } from '../servers/data';
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version'; import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
import { isReachableServer, SelectedServer } from '../servers/data';
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%'; const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable); const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);

View file

@ -1,7 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { SelectedServer } from '../servers/data'; import type { SelectedServer } from '../servers/data';
import type { Sidebar } from './reducers/sidebar';
import { ShlinkVersions } from './ShlinkVersions'; import { ShlinkVersions } from './ShlinkVersions';
import { Sidebar } from './reducers/sidebar';
import './ShlinkVersionsContainer.scss'; import './ShlinkVersionsContainer.scss';
export interface ShlinkVersionsContainerProps { export interface ShlinkVersionsContainerProps {

View file

@ -1,12 +1,13 @@
import { FC } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import type { FC } from 'react';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
import type {
NumberOrEllipsis } from '../utils/helpers/pagination';
import { import {
pageIsEllipsis,
keyForPage, keyForPage,
NumberOrEllipsis, pageIsEllipsis,
progressivePagination,
prettifyPageNumber, prettifyPageNumber,
progressivePagination,
} from '../utils/helpers/pagination'; } from '../utils/helpers/pagination';
import './SimplePaginator.scss'; import './SimplePaginator.scss';

View file

@ -1,4 +1,4 @@
import { Fetch } from '../../utils/types'; import type { Fetch } from '../../utils/types';
const applicationJsonHeader = { 'Content-Type': 'application/json' }; const applicationJsonHeader = { 'Content-Type': 'application/json' };
const withJsonContentType = (options?: RequestInit): RequestInit | undefined => { const withJsonContentType = (options?: RequestInit): RequestInit | undefined => {

View file

@ -1,5 +1,5 @@
import { saveUrl } from '../../utils/helpers/files'; import { saveUrl } from '../../utils/helpers/files';
import { HttpClient } from './HttpClient'; import type { HttpClient } from './HttpClient';
export class ImageDownloader { export class ImageDownloader {
public constructor(private readonly httpClient: HttpClient, private readonly window: Window) {} public constructor(private readonly httpClient: HttpClient, private readonly window: Window) {}

View file

@ -1,7 +1,7 @@
import { NormalizedVisit } from '../../visits/types'; import type { ExportableShortUrl } from '../../short-urls/data';
import { ExportableShortUrl } from '../../short-urls/data'; import type { JsonToCsv } from '../../utils/helpers/csvjson';
import { saveCsv } from '../../utils/helpers/files'; import { saveCsv } from '../../utils/helpers/files';
import { JsonToCsv } from '../../utils/helpers/csvjson'; import type { NormalizedVisit } from '../../visits/types';
export class ReportExporter { export class ReportExporter {
public constructor(private readonly window: Window, private readonly jsonToCsv: JsonToCsv) {} public constructor(private readonly window: Window, private readonly jsonToCsv: JsonToCsv) {}

View file

@ -1,19 +1,19 @@
import Bottle from 'bottlejs'; import type Bottle from 'bottlejs';
import { ScrollToTop } from '../ScrollToTop'; import type { ConnectDecorator } from '../../container/types';
import { MainHeader } from '../MainHeader'; import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { Home } from '../Home';
import { MenuLayout } from '../MenuLayout';
import { AsideMenu } from '../AsideMenu'; import { AsideMenu } from '../AsideMenu';
import { ErrorHandler } from '../ErrorHandler'; import { ErrorHandler } from '../ErrorHandler';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer'; import { Home } from '../Home';
import { ConnectDecorator } from '../../container/types'; import { MainHeader } from '../MainHeader';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer'; import { MenuLayout } from '../MenuLayout';
import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar'; import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar';
import { ScrollToTop } from '../ScrollToTop';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
import { HttpClient } from './HttpClient';
import { ImageDownloader } from './ImageDownloader'; import { ImageDownloader } from './ImageDownloader';
import { ReportExporter } from './ReportExporter'; import { ReportExporter } from './ReportExporter';
import { HttpClient } from './HttpClient';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Services // Services
bottle.constant('window', window); bottle.constant('window', window);
bottle.constant('console', console); bottle.constant('console', console);
@ -62,5 +62,3 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('sidebarPresent', () => sidebarPresent); bottle.serviceFactory('sidebarPresent', () => sidebarPresent);
bottle.serviceFactory('sidebarNotPresent', () => sidebarNotPresent); bottle.serviceFactory('sidebarNotPresent', () => sidebarNotPresent);
}; };
export default provideServices;

View file

@ -1,18 +1,19 @@
import Bottle, { IContainer } from 'bottlejs'; import type { IContainer } from 'bottlejs';
import { connect as reduxConnect } from 'react-redux'; import Bottle from 'bottlejs';
import { pick } from 'ramda'; import { pick } from 'ramda';
import provideApiServices from '../api/services/provideServices'; import { connect as reduxConnect } from 'react-redux';
import provideCommonServices from '../common/services/provideServices'; import { provideServices as provideApiServices } from '../api/services/provideServices';
import provideShortUrlsServices from '../short-urls/services/provideServices'; import { provideServices as provideAppServices } from '../app/services/provideServices';
import provideServersServices from '../servers/services/provideServices'; import { provideServices as provideCommonServices } from '../common/services/provideServices';
import provideVisitsServices from '../visits/services/provideServices'; import { provideServices as provideDomainsServices } from '../domains/services/provideServices';
import provideTagsServices from '../tags/services/provideServices'; import { provideServices as provideMercureServices } from '../mercure/services/provideServices';
import provideUtilsServices from '../utils/services/provideServices'; import { provideServices as provideServersServices } from '../servers/services/provideServices';
import provideMercureServices from '../mercure/services/provideServices'; import { provideServices as provideSettingsServices } from '../settings/services/provideServices';
import provideSettingsServices from '../settings/services/provideServices'; import { provideServices as provideShortUrlsServices } from '../short-urls/services/provideServices';
import provideDomainsServices from '../domains/services/provideServices'; import { provideServices as provideTagsServices } from '../tags/services/provideServices';
import provideAppServices from '../app/services/provideServices'; import { provideServices as provideUtilsServices } from '../utils/services/provideServices';
import { ConnectDecorator } from './types'; import { provideServices as provideVisitsServices } from '../visits/services/provideServices';
import type { ConnectDecorator } from './types';
type LazyActionMap = Record<string, Function>; type LazyActionMap = Record<string, Function>;

View file

@ -1,9 +1,10 @@
import { IContainer } from 'bottlejs';
import { save, load, RLSOptions } from 'redux-localstorage-simple';
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import reducer from '../reducers'; import type { IContainer } from 'bottlejs';
import type { RLSOptions } from 'redux-localstorage-simple';
import { load, save } from 'redux-localstorage-simple';
import { initReducers } from '../reducers';
import { migrateDeprecatedSettings } from '../settings/helpers'; import { migrateDeprecatedSettings } from '../settings/helpers';
import { ShlinkState } from './types'; import type { ShlinkState } from './types';
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const localStorageConfig: RLSOptions = { const localStorageConfig: RLSOptions = {
@ -16,7 +17,7 @@ const preloadedState = migrateDeprecatedSettings(load(localStorageConfig) as Shl
export const setUpStore = (container: IContainer) => configureStore({ export const setUpStore = (container: IContainer) => configureStore({
devTools: !isProduction, devTools: !isProduction,
reducer: reducer(container), reducer: initReducers(container),
preloadedState, preloadedState,
middleware: (defaultMiddlewaresIncludingReduxThunk) => middleware: (defaultMiddlewaresIncludingReduxThunk) =>
defaultMiddlewaresIncludingReduxThunk({ immutableCheck: false, serializableCheck: false }) // State is too big for these defaultMiddlewaresIncludingReduxThunk({ immutableCheck: false, serializableCheck: false }) // State is too big for these

View file

@ -1,21 +1,21 @@
import { MercureInfo } from '../mercure/reducers/mercureInfo'; import type { Sidebar } from '../common/reducers/sidebar';
import { SelectedServer, ServersMap } from '../servers/data'; import type { DomainsList } from '../domains/reducers/domainsList';
import { Settings } from '../settings/reducers/settings'; import type { MercureInfo } from '../mercure/reducers/mercureInfo';
import { ShortUrlCreation } from '../short-urls/reducers/shortUrlCreation'; import type { SelectedServer, ServersMap } from '../servers/data';
import { ShortUrlDeletion } from '../short-urls/reducers/shortUrlDeletion'; import type { Settings } from '../settings/reducers/settings';
import { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition'; import type { ShortUrlCreation } from '../short-urls/reducers/shortUrlCreation';
import { ShortUrlsList } from '../short-urls/reducers/shortUrlsList'; import type { ShortUrlDeletion } from '../short-urls/reducers/shortUrlDeletion';
import { TagDeletion } from '../tags/reducers/tagDelete'; import type { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
import { TagEdition } from '../tags/reducers/tagEdit'; import type { ShortUrlEdition } from '../short-urls/reducers/shortUrlEdition';
import { TagsList } from '../tags/reducers/tagsList'; import type { ShortUrlsList } from '../short-urls/reducers/shortUrlsList';
import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail'; import type { TagDeletion } from '../tags/reducers/tagDelete';
import { ShortUrlVisits } from '../visits/reducers/shortUrlVisits'; import type { TagEdition } from '../tags/reducers/tagEdit';
import { TagVisits } from '../visits/reducers/tagVisits'; import type { TagsList } from '../tags/reducers/tagsList';
import { DomainsList } from '../domains/reducers/domainsList'; import type { DomainVisits } from '../visits/reducers/domainVisits';
import { VisitsOverview } from '../visits/reducers/visitsOverview'; import type { ShortUrlVisits } from '../visits/reducers/shortUrlVisits';
import { Sidebar } from '../common/reducers/sidebar'; import type { TagVisits } from '../visits/reducers/tagVisits';
import { DomainVisits } from '../visits/reducers/domainVisits'; import type { VisitsInfo } from '../visits/reducers/types';
import { VisitsInfo } from '../visits/reducers/types'; import type { VisitsOverview } from '../visits/reducers/visitsOverview';
export interface ShlinkState { export interface ShlinkState {
servers: ServersMap; servers: ServersMap;

View file

@ -1,14 +1,15 @@
import { FC, useEffect } from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faDotCircle as defaultDomainIcon } from '@fortawesome/free-solid-svg-icons'; import { faDotCircle as defaultDomainIcon } from '@fortawesome/free-solid-svg-icons';
import { ShlinkDomainRedirects } from '../api/types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { OptionalString } from '../utils/utils'; import type { FC } from 'react';
import { SelectedServer } from '../servers/data'; import { useEffect } from 'react';
import { Domain } from './data'; import { UncontrolledTooltip } from 'reactstrap';
import { DomainStatusIcon } from './helpers/DomainStatusIcon'; import type { ShlinkDomainRedirects } from '../api/types';
import type { SelectedServer } from '../servers/data';
import type { OptionalString } from '../utils/utils';
import type { Domain } from './data';
import { DomainDropdown } from './helpers/DomainDropdown'; import { DomainDropdown } from './helpers/DomainDropdown';
import { EditDomainRedirects } from './reducers/domainRedirects'; import { DomainStatusIcon } from './helpers/DomainStatusIcon';
import type { EditDomainRedirects } from './reducers/domainRedirects';
interface DomainRowProps { interface DomainRowProps {
domain: Domain; domain: Domain;

View file

@ -1,11 +1,12 @@
import { useEffect } from 'react';
import { Button, DropdownItem, Input, InputGroup, UncontrolledTooltip, InputProps } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUndo } from '@fortawesome/free-solid-svg-icons'; import { faUndo } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, pipe } from 'ramda'; import { isEmpty, pipe } from 'ramda';
import { useEffect } from 'react';
import type { InputProps } from 'reactstrap';
import { Button, DropdownItem, Input, InputGroup, UncontrolledTooltip } from 'reactstrap';
import { DropdownBtn } from '../utils/DropdownBtn'; import { DropdownBtn } from '../utils/DropdownBtn';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { DomainsList } from './reducers/domainsList'; import type { DomainsList } from './reducers/domainsList';
import './DomainSelector.scss'; import './DomainSelector.scss';
export interface DomainSelectorProps extends Omit<InputProps, 'onChange'> { export interface DomainSelectorProps extends Omit<InputProps, 'onChange'> {

View file

@ -1,13 +1,14 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react';
import { ShlinkApiError } from '../api/ShlinkApiError';
import type { SelectedServer } from '../servers/data';
import { Message } from '../utils/Message'; import { Message } from '../utils/Message';
import { Result } from '../utils/Result'; import { Result } from '../utils/Result';
import { ShlinkApiError } from '../api/ShlinkApiError';
import { SimpleCard } from '../utils/SimpleCard';
import { SearchField } from '../utils/SearchField'; import { SearchField } from '../utils/SearchField';
import { EditDomainRedirects } from './reducers/domainRedirects'; import { SimpleCard } from '../utils/SimpleCard';
import { SelectedServer } from '../servers/data';
import { DomainsList } from './reducers/domainsList';
import { DomainRow } from './DomainRow'; import { DomainRow } from './DomainRow';
import type { EditDomainRedirects } from './reducers/domainRedirects';
import type { DomainsList } from './reducers/domainsList';
interface ManageDomainsProps { interface ManageDomainsProps {
listDomains: Function; listDomains: Function;

View file

@ -1,4 +1,4 @@
import { ShlinkDomain } from '../../api/types'; import type { ShlinkDomain } from '../../api/types';
export type DomainStatus = 'validating' | 'valid' | 'invalid'; export type DomainStatus = 'validating' | 'valid' | 'invalid';

View file

@ -1,16 +1,17 @@
import { FC } from 'react';
import { DropdownItem } from 'reactstrap';
import { Link } from 'react-router-dom';
import { faChartPie as pieChartIcon, faEdit as editIcon } from '@fortawesome/free-solid-svg-icons'; import { faChartPie as pieChartIcon, faEdit as editIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useToggle } from '../../utils/helpers/hooks'; import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { DropdownItem } from 'reactstrap';
import type { SelectedServer } from '../../servers/data';
import { getServerId } from '../../servers/data';
import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu'; import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu';
import { EditDomainRedirectsModal } from './EditDomainRedirectsModal';
import { Domain } from '../data';
import { EditDomainRedirects } from '../reducers/domainRedirects';
import { supportsDefaultDomainRedirectsEdition, supportsDomainVisits } from '../../utils/helpers/features'; import { supportsDefaultDomainRedirectsEdition, supportsDomainVisits } from '../../utils/helpers/features';
import { getServerId, SelectedServer } from '../../servers/data'; import { useToggle } from '../../utils/helpers/hooks';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import type { Domain } from '../data';
import type { EditDomainRedirects } from '../reducers/domainRedirects';
import { EditDomainRedirectsModal } from './EditDomainRedirectsModal';
interface DomainDropdownProps { interface DomainDropdownProps {
domain: Domain; domain: Domain;

View file

@ -1,15 +1,16 @@
import { FC, useEffect, useRef, useState } from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faTimes as invalidIcon,
faCheck as checkIcon, faCheck as checkIcon,
faCircleNotch as loadingStatusIcon, faCircleNotch as loadingStatusIcon,
faTimes as invalidIcon,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { MediaMatcher } from '../../utils/types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mutableRefToElementRef } from '../../utils/helpers/components'; import type { FC } from 'react';
import { DomainStatus } from '../data'; import { useEffect, useState } from 'react';
import { ExternalLink } from 'react-external-link';
import { UncontrolledTooltip } from 'reactstrap';
import { useElementRef } from '../../utils/helpers/hooks';
import type { MediaMatcher } from '../../utils/types';
import type { DomainStatus } from '../data';
interface DomainStatusIconProps { interface DomainStatusIconProps {
status: DomainStatus; status: DomainStatus;
@ -17,7 +18,7 @@ interface DomainStatusIconProps {
} }
export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => { export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => {
const ref = useRef<HTMLSpanElement>(); const ref = useElementRef<HTMLSpanElement>();
const matchesMobile = () => matchMedia('(max-width: 991px)').matches; const matchesMobile = () => matchMedia('(max-width: 991px)').matches;
const [isMobile, setIsMobile] = useState<boolean>(matchesMobile()); const [isMobile, setIsMobile] = useState<boolean>(matchesMobile());
@ -35,13 +36,13 @@ export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia
return ( return (
<> <>
<span ref={mutableRefToElementRef(ref)}> <span ref={ref}>
{status === 'valid' {status === 'valid'
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" /> ? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />} : <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
</span> </span>
<UncontrolledTooltip <UncontrolledTooltip
target={(() => ref.current) as any} target={ref}
placement={isMobile ? 'top-start' : 'left'} placement={isMobile ? 'top-start' : 'left'}
autohide={status === 'valid'} autohide={status === 'valid'}
> >

View file

@ -1,10 +1,12 @@
import { FC, useState } from 'react'; import type { FC } from 'react';
import { useState } from 'react';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ShlinkDomain } from '../../api/types'; import type { ShlinkDomain } from '../../api/types';
import { InputFormGroup, InputFormGroupProps } from '../../utils/forms/InputFormGroup'; import type { InputFormGroupProps } from '../../utils/forms/InputFormGroup';
import { handleEventPreventingDefault, nonEmptyValueOrNull } from '../../utils/utils'; import { InputFormGroup } from '../../utils/forms/InputFormGroup';
import { InfoTooltip } from '../../utils/InfoTooltip'; import { InfoTooltip } from '../../utils/InfoTooltip';
import { EditDomainRedirects } from '../reducers/domainRedirects'; import { handleEventPreventingDefault, nonEmptyValueOrNull } from '../../utils/utils';
import type { EditDomainRedirects } from '../reducers/domainRedirects';
interface EditDomainRedirectsModalProps { interface EditDomainRedirectsModalProps {
domain: ShlinkDomain; domain: ShlinkDomain;

View file

@ -1,6 +1,6 @@
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import type { ShlinkDomainRedirects } from '../../api/types';
import { createAsyncThunk } from '../../utils/helpers/redux'; import { createAsyncThunk } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { ShlinkDomainRedirects } from '../../api/types';
const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS'; const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS';

View file

@ -1,13 +1,14 @@
import { createSlice, createAction, SliceCaseReducers, AsyncThunk } from '@reduxjs/toolkit'; import type { AsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
import { createAsyncThunk } from '../../utils/helpers/redux'; import { createAction, createSlice } from '@reduxjs/toolkit';
import { ShlinkDomainRedirects } from '../../api/types'; import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import type { ShlinkDomainRedirects } from '../../api/types';
import { Domain, DomainStatus } from '../data'; import type { ProblemDetailsError } from '../../api/types/errors';
import { hasServerData } from '../../servers/data';
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
import { ProblemDetailsError } from '../../api/types/errors';
import { parseApiError } from '../../api/utils'; import { parseApiError } from '../../api/utils';
import { EditDomainRedirects } from './domainRedirects'; import { hasServerData } from '../../servers/data';
import { createAsyncThunk } from '../../utils/helpers/redux';
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
import type { Domain, DomainStatus } from '../data';
import type { EditDomainRedirects } from './domainRedirects';
const REDUCER_PREFIX = 'shlink/domainsList'; const REDUCER_PREFIX = 'shlink/domainsList';

View file

@ -1,12 +1,12 @@
import type Bottle from 'bottlejs';
import { prop } from 'ramda'; import { prop } from 'ramda';
import Bottle from 'bottlejs'; import type { ConnectDecorator } from '../../container/types';
import { ConnectDecorator } from '../../container/types';
import { domainsListReducerCreator } from '../reducers/domainsList';
import { DomainSelector } from '../DomainSelector'; import { DomainSelector } from '../DomainSelector';
import { ManageDomains } from '../ManageDomains'; import { ManageDomains } from '../ManageDomains';
import { editDomainRedirects } from '../reducers/domainRedirects'; import { editDomainRedirects } from '../reducers/domainRedirects';
import { domainsListReducerCreator } from '../reducers/domainsList';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components // Components
bottle.serviceFactory('DomainSelector', () => DomainSelector); bottle.serviceFactory('DomainSelector', () => DomainSelector);
bottle.decorator('DomainSelector', connect(['domainsList'], ['listDomains'])); bottle.decorator('DomainSelector', connect(['domainsList'], ['listDomains']));
@ -32,5 +32,3 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient'); bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient');
bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsListReducerCreator'); bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsListReducerCreator');
}; };
export default provideServices;

View file

@ -1,15 +1,15 @@
import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import pack from '../package.json'; import pack from '../package.json';
import { container } from './container'; import { container } from './container';
import { setUpStore } from './container/store'; import { setUpStore } from './container/store';
import { fixLeafletIcons } from './utils/helpers/leaflet';
import { register as registerServiceWorker } from './serviceWorkerRegistration'; import { register as registerServiceWorker } from './serviceWorkerRegistration';
import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking import { fixLeafletIcons } from './utils/helpers/leaflet';
import 'react-datepicker/dist/react-datepicker.css';
import 'leaflet/dist/leaflet.css';
import './index.scss'; import './index.scss';
import 'leaflet/dist/leaflet.css';
import 'react-datepicker/dist/react-datepicker.css';
// This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS // This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS
fixLeafletIcons(); fixLeafletIcons();

View file

@ -1,8 +1,9 @@
import { FC, useEffect } from 'react';
import { pipe } from 'ramda'; import { pipe } from 'ramda';
import type { FC } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { CreateVisit } from '../../visits/types'; import type { CreateVisit } from '../../visits/types';
import { MercureInfo } from '../reducers/mercureInfo'; import type { MercureInfo } from '../reducers/mercureInfo';
import { bindToMercureTopic } from './index'; import { bindToMercureTopic } from './index';
export interface MercureBoundProps { export interface MercureBoundProps {

View file

@ -1,5 +1,5 @@
import { EventSourcePolyfill as EventSource } from 'event-source-polyfill'; import { EventSourcePolyfill as EventSource } from 'event-source-polyfill';
import { MercureInfo } from '../reducers/mercureInfo'; import type { MercureInfo } from '../reducers/mercureInfo';
export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topics: string[], onMessage: (message: T) => void, onTokenExpired: () => void) => { // eslint-disable-line max-len export const bindToMercureTopic = <T>(mercureInfo: MercureInfo, topics: string[], onMessage: (message: T) => void, onTokenExpired: () => void) => { // eslint-disable-line max-len
const { mercureHubUrl, token, loading, error } = mercureInfo; const { mercureHubUrl, token, loading, error } = mercureInfo;

View file

@ -1,7 +1,7 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import type { ShlinkMercureInfo } from '../../api/types';
import { createAsyncThunk } from '../../utils/helpers/redux'; import { createAsyncThunk } from '../../utils/helpers/redux';
import { ShlinkMercureInfo } from '../../api/types';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
const REDUCER_PREFIX = 'shlink/mercure'; const REDUCER_PREFIX = 'shlink/mercure';

View file

@ -1,8 +1,8 @@
import type Bottle from 'bottlejs';
import { prop } from 'ramda'; import { prop } from 'ramda';
import Bottle from 'bottlejs';
import { mercureInfoReducerCreator } from '../reducers/mercureInfo'; import { mercureInfoReducerCreator } from '../reducers/mercureInfo';
const provideServices = (bottle: Bottle) => { export const provideServices = (bottle: Bottle) => {
// Reducer // Reducer
bottle.serviceFactory('mercureInfoReducerCreator', mercureInfoReducerCreator, 'buildShlinkApiClient'); bottle.serviceFactory('mercureInfoReducerCreator', mercureInfoReducerCreator, 'buildShlinkApiClient');
bottle.serviceFactory('mercureInfoReducer', prop('reducer'), 'mercureInfoReducerCreator'); bottle.serviceFactory('mercureInfoReducer', prop('reducer'), 'mercureInfoReducerCreator');
@ -10,5 +10,3 @@ const provideServices = (bottle: Bottle) => {
// Actions // Actions
bottle.serviceFactory('loadMercureInfo', prop('loadMercureInfo'), 'mercureInfoReducerCreator'); bottle.serviceFactory('loadMercureInfo', prop('loadMercureInfo'), 'mercureInfoReducerCreator');
}; };
export default provideServices;

View file

@ -1,12 +1,12 @@
import { IContainer } from 'bottlejs';
import { combineReducers } from '@reduxjs/toolkit'; import { combineReducers } from '@reduxjs/toolkit';
import { serversReducer } from '../servers/reducers/servers'; import type { IContainer } from 'bottlejs';
import { settingsReducer } from '../settings/reducers/settings';
import { appUpdatesReducer } from '../app/reducers/appUpdates'; import { appUpdatesReducer } from '../app/reducers/appUpdates';
import { sidebarReducer } from '../common/reducers/sidebar'; import { sidebarReducer } from '../common/reducers/sidebar';
import { ShlinkState } from '../container/types'; import type { ShlinkState } from '../container/types';
import { serversReducer } from '../servers/reducers/servers';
import { settingsReducer } from '../settings/reducers/settings';
export default (container: IContainer) => combineReducers<ShlinkState>({ export const initReducers = (container: IContainer) => combineReducers<ShlinkState>({
servers: serversReducer, servers: serversReducer,
selectedServer: container.selectedServerReducer, selectedServer: container.selectedServerReducer,
shortUrlsList: container.shortUrlsListReducer, shortUrlsList: container.shortUrlsListReducer,

View file

@ -1,14 +1,16 @@
import { FC, useEffect, useState } from 'react'; import type { FC } from 'react';
import { v4 as uuid } from 'uuid'; import { useEffect, useState } from 'react';
import { Button } from 'reactstrap';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Result } from '../utils/Result'; import { Button } from 'reactstrap';
import { v4 as uuid } from 'uuid';
import { NoMenuLayout } from '../common/NoMenuLayout'; import { NoMenuLayout } from '../common/NoMenuLayout';
import { TimeoutToggle, useGoBack, useToggle } from '../utils/helpers/hooks'; import type { TimeoutToggle } from '../utils/helpers/hooks';
import { ServerForm } from './helpers/ServerForm'; import { useGoBack, useToggle } from '../utils/helpers/hooks';
import { ImportServersBtnProps } from './helpers/ImportServersBtn'; import { Result } from '../utils/Result';
import { ServerData, ServersMap, ServerWithId } from './data'; import type { ServerData, ServersMap, ServerWithId } from './data';
import { DuplicatedServersModal } from './helpers/DuplicatedServersModal'; import { DuplicatedServersModal } from './helpers/DuplicatedServersModal';
import type { ImportServersBtnProps } from './helpers/ImportServersBtn';
import { ServerForm } from './helpers/ServerForm';
const SHOW_IMPORT_MSG_TIME = 4000; const SHOW_IMPORT_MSG_TIME = 4000;

View file

@ -1,9 +1,9 @@
import { FC, PropsWithChildren } from 'react';
import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons'; import { faMinusCircle as deleteIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC, PropsWithChildren } from 'react';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { DeleteServerModalProps } from './DeleteServerModal'; import type { ServerWithId } from './data';
import { ServerWithId } from './data'; import type { DeleteServerModalProps } from './DeleteServerModal';
export type DeleteServerButtonProps = PropsWithChildren<{ export type DeleteServerButtonProps = PropsWithChildren<{
server: ServerWithId; server: ServerWithId;

View file

@ -1,7 +1,8 @@
import { FC, useRef } from 'react'; import type { FC } from 'react';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { useRef } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ServerWithId } from './data'; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import type { ServerWithId } from './data';
export interface DeleteServerModalProps { export interface DeleteServerModalProps {
server: ServerWithId; server: ServerWithId;

View file

@ -1,10 +1,11 @@
import { FC } from 'react'; import type { FC } from 'react';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { NoMenuLayout } from '../common/NoMenuLayout'; import { NoMenuLayout } from '../common/NoMenuLayout';
import { useGoBack, useParsedQuery } from '../utils/helpers/hooks'; import { useGoBack, useParsedQuery } from '../utils/helpers/hooks';
import type { ServerData } from './data';
import { isServerWithId } from './data';
import { ServerForm } from './helpers/ServerForm'; import { ServerForm } from './helpers/ServerForm';
import { withSelectedServer } from './helpers/withSelectedServer'; import { withSelectedServer } from './helpers/withSelectedServer';
import { isServerWithId, ServerData } from './data';
interface EditServerProps { interface EditServerProps {
editServer: (serverId: string, serverData: ServerData) => void; editServer: (serverId: string, serverData: ServerData) => void;

View file

@ -1,17 +1,18 @@
import { FC, useEffect, useState } from 'react';
import { Button, Row } from 'reactstrap';
import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons'; import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button, Row } from 'reactstrap';
import { NoMenuLayout } from '../common/NoMenuLayout'; import { NoMenuLayout } from '../common/NoMenuLayout';
import { SimpleCard } from '../utils/SimpleCard'; import type { TimeoutToggle } from '../utils/helpers/hooks';
import { SearchField } from '../utils/SearchField';
import { Result } from '../utils/Result'; import { Result } from '../utils/Result';
import { TimeoutToggle } from '../utils/helpers/hooks'; import { SearchField } from '../utils/SearchField';
import { ImportServersBtnProps } from './helpers/ImportServersBtn'; import { SimpleCard } from '../utils/SimpleCard';
import { ServersMap } from './data'; import type { ServersMap } from './data';
import { ManageServersRowProps } from './ManageServersRow'; import type { ImportServersBtnProps } from './helpers/ImportServersBtn';
import ServersExporter from './services/ServersExporter'; import type { ManageServersRowProps } from './ManageServersRow';
import type { ServersExporter } from './services/ServersExporter';
interface ManageServersProps { interface ManageServersProps {
servers: ServersMap; servers: ServersMap;

View file

@ -1,10 +1,10 @@
import { FC } from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheck as checkIcon } from '@fortawesome/free-solid-svg-icons'; import { faCheck as checkIcon } from '@fortawesome/free-solid-svg-icons';
import { ServerWithId } from './data'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ManageServersRowDropdownProps } from './ManageServersRowDropdown'; import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { UncontrolledTooltip } from 'reactstrap';
import type { ServerWithId } from './data';
import type { ManageServersRowDropdownProps } from './ManageServersRowDropdown';
export interface ManageServersRowProps { export interface ManageServersRowProps {
server: ServerWithId; server: ServerWithId;

View file

@ -1,18 +1,18 @@
import { FC } from 'react'; import { faCircle as toggleOnIcon } from '@fortawesome/free-regular-svg-icons';
import { DropdownItem } from 'reactstrap';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faBan as toggleOffIcon, faBan as toggleOffIcon,
faEdit as editIcon, faEdit as editIcon,
faMinusCircle as deleteIcon, faMinusCircle as deleteIcon,
faPlug as connectIcon, faPlug as connectIcon,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { faCircle as toggleOnIcon } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { DropdownItem } from 'reactstrap';
import { DropdownBtnMenu } from '../utils/DropdownBtnMenu'; import { DropdownBtnMenu } from '../utils/DropdownBtnMenu';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { DeleteServerModalProps } from './DeleteServerModal'; import type { ServerWithId } from './data';
import { ServerWithId } from './data'; import type { DeleteServerModalProps } from './DeleteServerModal';
export interface ManageServersRowDropdownProps { export interface ManageServersRowDropdownProps {
server: ServerWithId; server: ServerWithId;

View file

@ -1,17 +1,20 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { Card, CardBody, CardHeader, Row } from 'reactstrap'; import { useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList'; import { Card, CardBody, CardHeader, Row } from 'reactstrap';
import { prettify } from '../utils/helpers/numbers'; import type { ShlinkShortUrlsListParams } from '../api/types';
import { TagsList } from '../tags/reducers/tagsList';
import { ShortUrlsTableType } from '../short-urls/ShortUrlsTable';
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
import { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
import { VisitsOverview } from '../visits/reducers/visitsOverview';
import { Topics } from '../mercure/helpers/Topics'; import { Topics } from '../mercure/helpers/Topics';
import { ShlinkShortUrlsListParams } from '../api/types'; import type { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
import type { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList';
import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList';
import type { ShortUrlsTableType } from '../short-urls/ShortUrlsTable';
import type { TagsList } from '../tags/reducers/tagsList';
import { supportsNonOrphanVisits } from '../utils/helpers/features'; import { supportsNonOrphanVisits } from '../utils/helpers/features';
import { getServerId, SelectedServer } from './data'; import { prettify } from '../utils/helpers/numbers';
import type { VisitsOverview } from '../visits/reducers/visitsOverview';
import type { SelectedServer } from './data';
import { getServerId } from './data';
import { HighlightCard } from './helpers/HighlightCard'; import { HighlightCard } from './helpers/HighlightCard';
interface OverviewConnectProps { interface OverviewConnectProps {

View file

@ -1,9 +1,10 @@
import { isEmpty, values } from 'ramda';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
import { Link } from 'react-router-dom';
import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons'; import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { getServerId, SelectedServer, ServersMap } from './data'; import { isEmpty, values } from 'ramda';
import { Link } from 'react-router-dom';
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
import type { SelectedServer, ServersMap } from './data';
import { getServerId } from './data';
export interface ServersDropdownProps { export interface ServersDropdownProps {
servers: ServersMap; servers: ServersMap;

View file

@ -1,10 +1,10 @@
import { FC, PropsWithChildren } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons'; import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
import { ServerWithId } from './data'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import type { FC, PropsWithChildren } from 'react';
import { Link } from 'react-router-dom';
import { ListGroup, ListGroupItem } from 'reactstrap';
import type { ServerWithId } from './data';
import './ServersListGroup.scss'; import './ServersListGroup.scss';
type ServersListGroupProps = PropsWithChildren<{ type ServersListGroupProps = PropsWithChildren<{

View file

@ -1,5 +1,5 @@
import { omit } from 'ramda'; import { omit } from 'ramda';
import { SemVer } from '../../utils/helpers/version'; import type { SemVer } from '../../utils/helpers/version';
export interface ServerData { export interface ServerData {
name: string; name: string;

View file

@ -1,6 +1,7 @@
import { FC, Fragment } from 'react'; import type { FC } from 'react';
import { Fragment } from 'react';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ServerData } from '../data'; import type { ServerData } from '../data';
interface DuplicatedServersModalProps { interface DuplicatedServersModalProps {
duplicatedServers: ServerData[]; duplicatedServers: ServerData[];

View file

@ -1,8 +1,8 @@
import { FC, PropsWithChildren } from 'react';
import { Card, CardText, CardTitle } from 'reactstrap';
import { Link } from 'react-router-dom';
import { faArrowAltCircleRight as linkIcon } from '@fortawesome/free-regular-svg-icons'; import { faArrowAltCircleRight as linkIcon } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC, PropsWithChildren } from 'react';
import { Link } from 'react-router-dom';
import { Card, CardText, CardTitle } from 'reactstrap';
import './HighlightCard.scss'; import './HighlightCard.scss';
export type HighlightCardProps = PropsWithChildren<{ export type HighlightCardProps = PropsWithChildren<{

View file

@ -1,12 +1,12 @@
import { useRef, ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap';
import { complement, pipe } from 'ramda';
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons'; import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useToggle } from '../../utils/helpers/hooks'; import { complement, pipe } from 'ramda';
import { mutableRefToElementRef } from '../../utils/helpers/components'; import type { ChangeEvent, FC, PropsWithChildren } from 'react';
import { ServersImporter } from '../services/ServersImporter'; import { useEffect, useState } from 'react';
import { ServerData, ServersMap } from '../data'; import { Button, UncontrolledTooltip } from 'reactstrap';
import { useElementRef, useToggle } from '../../utils/helpers/hooks';
import type { ServerData, ServersMap } from '../data';
import type { ServersImporter } from '../services/ServersImporter';
import { DuplicatedServersModal } from './DuplicatedServersModal'; import { DuplicatedServersModal } from './DuplicatedServersModal';
import './ImportServersBtn.scss'; import './ImportServersBtn.scss';
@ -34,7 +34,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
tooltipPlacement = 'bottom', tooltipPlacement = 'bottom',
className = '', className = '',
}) => { }) => {
const ref = useRef<HTMLInputElement>(); const ref = useElementRef<HTMLInputElement>();
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>(); const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]); const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
const [isModalOpen,, showModal, hideModal] = useToggle(); const [isModalOpen,, showModal, hideModal] = useToggle();
@ -79,7 +79,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
type="file" type="file"
accept="text/csv" accept="text/csv"
className="import-servers-btn__csv-select" className="import-servers-btn__csv-select"
ref={mutableRefToElementRef(ref)} ref={ref}
onChange={onFile} onChange={onFile}
/> />

View file

@ -1,10 +1,11 @@
import { FC } from 'react'; import type { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Message } from '../../utils/Message';
import { ServersListGroup } from '../ServersListGroup';
import { DeleteServerButtonProps } from '../DeleteServerButton';
import { isServerWithId, SelectedServer, ServersMap } from '../data';
import { NoMenuLayout } from '../../common/NoMenuLayout'; import { NoMenuLayout } from '../../common/NoMenuLayout';
import { Message } from '../../utils/Message';
import type { SelectedServer, ServersMap } from '../data';
import { isServerWithId } from '../data';
import type { DeleteServerButtonProps } from '../DeleteServerButton';
import { ServersListGroup } from '../ServersListGroup';
import './ServerError.scss'; import './ServerError.scss';
interface ServerErrorProps { interface ServerErrorProps {

View file

@ -1,8 +1,9 @@
import { FC, PropsWithChildren, ReactNode, useEffect, useState } from 'react'; import type { FC, PropsWithChildren, ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { InputFormGroup } from '../../utils/forms/InputFormGroup'; import { InputFormGroup } from '../../utils/forms/InputFormGroup';
import { handleEventPreventingDefault } from '../../utils/utils';
import { ServerData } from '../data';
import { SimpleCard } from '../../utils/SimpleCard'; import { SimpleCard } from '../../utils/SimpleCard';
import { handleEventPreventingDefault } from '../../utils/utils';
import type { ServerData } from '../data';
type ServerFormProps = PropsWithChildren<{ type ServerFormProps = PropsWithChildren<{
onSubmit: (server: ServerData) => void; onSubmit: (server: ServerData) => void;

View file

@ -1,8 +1,10 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Message } from '../../utils/Message';
import { isNotFoundServer, SelectedServer } from '../data';
import { NoMenuLayout } from '../../common/NoMenuLayout'; import { NoMenuLayout } from '../../common/NoMenuLayout';
import { Message } from '../../utils/Message';
import type { SelectedServer } from '../data';
import { isNotFoundServer } from '../data';
interface WithSelectedServerProps { interface WithSelectedServerProps {
selectServer: (serverId: string) => void; selectServer: (serverId: string) => void;

View file

@ -1,4 +1,5 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react';
interface WithoutSelectedServerProps { interface WithoutSelectedServerProps {
resetSelectedServer: Function; resetSelectedServer: Function;

View file

@ -1,8 +1,9 @@
import pack from '../../../package.json'; import pack from '../../../package.json';
import { hasServerData, ServerData } from '../data'; import type { HttpClient } from '../../common/services/HttpClient';
import { createServers } from './servers';
import { createAsyncThunk } from '../../utils/helpers/redux'; import { createAsyncThunk } from '../../utils/helpers/redux';
import { HttpClient } from '../../common/services/HttpClient'; import type { ServerData } from '../data';
import { hasServerData } from '../data';
import { createServers } from './servers';
const responseToServersList = (data: any): ServerData[] => (Array.isArray(data) ? data.filter(hasServerData) : []); const responseToServersList = (data: any): ServerData[] => (Array.isArray(data) ? data.filter(hasServerData) : []);

View file

@ -1,10 +1,12 @@
import { createAction, createListenerMiddleware, createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createAction, createListenerMiddleware, createSlice } from '@reduxjs/toolkit';
import { memoizeWith, pipe } from 'ramda'; import { memoizeWith, pipe } from 'ramda';
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import { isReachableServer, SelectedServer, ServerWithId } from '../data'; import type { ShlinkHealth } from '../../api/types';
import { ShlinkHealth } from '../../api/types';
import { createAsyncThunk } from '../../utils/helpers/redux'; import { createAsyncThunk } from '../../utils/helpers/redux';
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
import type { SelectedServer, ServerWithId } from '../data';
import { isReachableServer } from '../data';
const REDUCER_PREFIX = 'shlink/selectedServer'; const REDUCER_PREFIX = 'shlink/selectedServer';

View file

@ -1,7 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda'; import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ServerData, ServersMap, ServerWithId } from '../data'; import type { ServerData, ServersMap, ServerWithId } from '../data';
interface EditServer { interface EditServer {
serverId: string; serverId: string;

View file

@ -1,12 +1,13 @@
import { values } from 'ramda'; import { values } from 'ramda';
import { LocalStorage } from '../../utils/services/LocalStorage'; import type { JsonToCsv } from '../../utils/helpers/csvjson';
import { ServersMap, serverWithIdToServerData } from '../data';
import { saveCsv } from '../../utils/helpers/files'; import { saveCsv } from '../../utils/helpers/files';
import { JsonToCsv } from '../../utils/helpers/csvjson'; import type { LocalStorage } from '../../utils/services/LocalStorage';
import type { ServersMap } from '../data';
import { serverWithIdToServerData } from '../data';
const SERVERS_FILENAME = 'shlink-servers.csv'; const SERVERS_FILENAME = 'shlink-servers.csv';
export default class ServersExporter { export class ServersExporter {
public constructor( public constructor(
private readonly storage: LocalStorage, private readonly storage: LocalStorage,
private readonly window: Window, private readonly window: Window,

View file

@ -1,5 +1,5 @@
import { ServerData } from '../data'; import type { CsvToJson } from '../../utils/helpers/csvjson';
import { CsvToJson } from '../../utils/helpers/csvjson'; import type { ServerData } from '../data';
const validateServer = (server: any): server is ServerData => const validateServer = (server: any): server is ServerData =>
typeof server.url === 'string' && typeof server.apiKey === 'string' && typeof server.name === 'string'; typeof server.url === 'string' && typeof server.apiKey === 'string' && typeof server.name === 'string';

View file

@ -1,11 +1,18 @@
import type Bottle from 'bottlejs';
import { prop } from 'ramda'; import { prop } from 'ramda';
import Bottle from 'bottlejs'; import type { ConnectDecorator } from '../../container/types';
import { CreateServer } from '../CreateServer'; import { CreateServer } from '../CreateServer';
import { ServersDropdown } from '../ServersDropdown';
import { DeleteServerModal } from '../DeleteServerModal';
import { DeleteServerButton } from '../DeleteServerButton'; import { DeleteServerButton } from '../DeleteServerButton';
import { DeleteServerModal } from '../DeleteServerModal';
import { EditServer } from '../EditServer'; import { EditServer } from '../EditServer';
import { ImportServersBtn } from '../helpers/ImportServersBtn'; import { ImportServersBtn } from '../helpers/ImportServersBtn';
import { ServerError } from '../helpers/ServerError';
import { withoutSelectedServer } from '../helpers/withoutSelectedServer';
import { ManageServers } from '../ManageServers';
import { ManageServersRow } from '../ManageServersRow';
import { ManageServersRowDropdown } from '../ManageServersRowDropdown';
import { Overview } from '../Overview';
import { fetchServers } from '../reducers/remoteServers';
import { import {
resetSelectedServer, resetSelectedServer,
selectedServerReducerCreator, selectedServerReducerCreator,
@ -13,18 +20,11 @@ import {
selectServerListener, selectServerListener,
} from '../reducers/selectedServer'; } from '../reducers/selectedServer';
import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers'; import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
import { fetchServers } from '../reducers/remoteServers'; import { ServersDropdown } from '../ServersDropdown';
import { ServerError } from '../helpers/ServerError'; import { ServersExporter } from './ServersExporter';
import { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../helpers/withoutSelectedServer';
import { Overview } from '../Overview';
import { ManageServers } from '../ManageServers';
import { ManageServersRow } from '../ManageServersRow';
import { ManageServersRowDropdown } from '../ManageServersRowDropdown';
import { ServersImporter } from './ServersImporter'; import { ServersImporter } from './ServersImporter';
import ServersExporter from './ServersExporter';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components // Components
bottle.serviceFactory( bottle.serviceFactory(
'ManageServers', 'ManageServers',
@ -89,5 +89,3 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('selectedServerReducerCreator', selectedServerReducerCreator, 'selectServer'); bottle.serviceFactory('selectedServerReducerCreator', selectedServerReducerCreator, 'selectServer');
bottle.serviceFactory('selectedServerReducer', prop('reducer'), 'selectedServerReducerCreator'); bottle.serviceFactory('selectedServerReducer', prop('reducer'), 'selectedServerReducerCreator');
}; };
export default provideServices;

View file

@ -1,11 +1,11 @@
import { FormGroup, Input } from 'reactstrap';
import classNames from 'classnames'; import classNames from 'classnames';
import { ToggleSwitch } from '../utils/ToggleSwitch'; import { FormGroup, Input } from 'reactstrap';
import { SimpleCard } from '../utils/SimpleCard';
import { FormText } from '../utils/forms/FormText'; import { FormText } from '../utils/forms/FormText';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { Settings } from './reducers/settings';
import { useDomId } from '../utils/helpers/hooks'; import { useDomId } from '../utils/helpers/hooks';
import { SimpleCard } from '../utils/SimpleCard';
import { ToggleSwitch } from '../utils/ToggleSwitch';
import type { Settings } from './reducers/settings';
interface RealTimeUpdatesProps { interface RealTimeUpdatesProps {
settings: Settings; settings: Settings;

View file

@ -1,5 +1,5 @@
import { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import { NoMenuLayout } from '../common/NoMenuLayout'; import { NoMenuLayout } from '../common/NoMenuLayout';
import { NavPillItem, NavPills } from '../utils/NavPills'; import { NavPillItem, NavPills } from '../utils/NavPills';

View file

@ -1,11 +1,11 @@
import { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { DropdownItem, FormGroup } from 'reactstrap'; import { DropdownItem, FormGroup } from 'reactstrap';
import { SimpleCard } from '../utils/SimpleCard';
import { ToggleSwitch } from '../utils/ToggleSwitch';
import { DropdownBtn } from '../utils/DropdownBtn'; import { DropdownBtn } from '../utils/DropdownBtn';
import { FormText } from '../utils/forms/FormText'; import { FormText } from '../utils/forms/FormText';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { Settings, ShortUrlCreationSettings as ShortUrlsSettings, TagFilteringMode } from './reducers/settings'; import { SimpleCard } from '../utils/SimpleCard';
import { ToggleSwitch } from '../utils/ToggleSwitch';
import type { Settings, ShortUrlCreationSettings as ShortUrlsSettings, TagFilteringMode } from './reducers/settings';
interface ShortUrlCreationProps { interface ShortUrlCreationProps {
settings: Settings; settings: Settings;

View file

@ -1,9 +1,10 @@
import { FC } from 'react'; import type { FC } from 'react';
import { OrderingDropdown } from '../utils/OrderingDropdown';
import { SHORT_URLS_ORDERABLE_FIELDS } from '../short-urls/data'; import { SHORT_URLS_ORDERABLE_FIELDS } from '../short-urls/data';
import { SimpleCard } from '../utils/SimpleCard';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings'; import { OrderingDropdown } from '../utils/OrderingDropdown';
import { SimpleCard } from '../utils/SimpleCard';
import type { Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings';
import { DEFAULT_SHORT_URLS_ORDERING } from './reducers/settings';
interface ShortUrlsListSettingsProps { interface ShortUrlsListSettingsProps {
settings: Settings; settings: Settings;

View file

@ -1,9 +1,9 @@
import { FC } from 'react'; import type { FC } from 'react';
import { SimpleCard } from '../utils/SimpleCard';
import { OrderingDropdown } from '../utils/OrderingDropdown';
import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps'; import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings'; import { OrderingDropdown } from '../utils/OrderingDropdown';
import { SimpleCard } from '../utils/SimpleCard';
import type { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings';
interface TagsProps { interface TagsProps {
settings: Settings; settings: Settings;

View file

@ -1,10 +1,11 @@
import { FC } from 'react'; import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSun, faMoon } from '@fortawesome/free-solid-svg-icons'; import type { FC } from 'react';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';
import type { Theme } from '../utils/theme';
import { changeThemeInMarkup } from '../utils/theme';
import { ToggleSwitch } from '../utils/ToggleSwitch'; import { ToggleSwitch } from '../utils/ToggleSwitch';
import { changeThemeInMarkup, Theme } from '../utils/theme'; import type { Settings, UiSettings } from './reducers/settings';
import { Settings, UiSettings } from './reducers/settings';
import './UserInterfaceSettings.scss'; import './UserInterfaceSettings.scss';
interface UserInterfaceProps { interface UserInterfaceProps {

View file

@ -1,12 +1,12 @@
import { FC } from 'react'; import type { FC } from 'react';
import { FormGroup } from 'reactstrap'; import { FormGroup } from 'reactstrap';
import { SimpleCard } from '../utils/SimpleCard';
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector'; import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { Settings, VisitsSettings as VisitsSettingsConfig } from './reducers/settings';
import { ToggleSwitch } from '../utils/ToggleSwitch';
import { FormText } from '../utils/forms/FormText'; import { FormText } from '../utils/forms/FormText';
import { DateInterval } from '../utils/helpers/dateIntervals'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import type { DateInterval } from '../utils/helpers/dateIntervals';
import { SimpleCard } from '../utils/SimpleCard';
import { ToggleSwitch } from '../utils/ToggleSwitch';
import type { Settings, VisitsSettings as VisitsSettingsConfig } from './reducers/settings';
interface VisitsProps { interface VisitsProps {
settings: Settings; settings: Settings;

View file

@ -1,4 +1,4 @@
import { ShlinkState } from '../../container/types'; import type { ShlinkState } from '../../container/types';
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
export const migrateDeprecatedSettings = (state: Partial<ShlinkState>): Partial<ShlinkState> => { export const migrateDeprecatedSettings = (state: Partial<ShlinkState>): Partial<ShlinkState> => {

View file

@ -1,9 +1,10 @@
import { createSlice, PayloadAction, PrepareAction } from '@reduxjs/toolkit'; import type { PayloadAction, PrepareAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { mergeDeepRight } from 'ramda'; import { mergeDeepRight } from 'ramda';
import { Theme } from '../../utils/theme'; import type { ShortUrlsOrder } from '../../short-urls/data';
import { DateInterval } from '../../utils/helpers/dateIntervals'; import type { TagsOrder } from '../../tags/data/TagsListChildrenProps';
import { TagsOrder } from '../../tags/data/TagsListChildrenProps'; import type { DateInterval } from '../../utils/helpers/dateIntervals';
import { ShortUrlsOrder } from '../../short-urls/data'; import type { Theme } from '../../utils/theme';
export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = { export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = {
field: 'dateCreated', field: 'dateCreated',

View file

@ -1,6 +1,7 @@
import Bottle from 'bottlejs'; import type Bottle from 'bottlejs';
import type { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { RealTimeUpdatesSettings } from '../RealTimeUpdatesSettings'; import { RealTimeUpdatesSettings } from '../RealTimeUpdatesSettings';
import { Settings } from '../Settings';
import { import {
setRealTimeUpdatesInterval, setRealTimeUpdatesInterval,
setShortUrlCreationSettings, setShortUrlCreationSettings,
@ -10,15 +11,14 @@ import {
setVisitsSettings, setVisitsSettings,
toggleRealTimeUpdates, toggleRealTimeUpdates,
} from '../reducers/settings'; } from '../reducers/settings';
import { ConnectDecorator } from '../../container/types'; import { Settings } from '../Settings';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { ShortUrlCreationSettings } from '../ShortUrlCreationSettings'; import { ShortUrlCreationSettings } from '../ShortUrlCreationSettings';
import { ShortUrlsListSettings } from '../ShortUrlsListSettings';
import { TagsSettings } from '../TagsSettings';
import { UserInterfaceSettings } from '../UserInterfaceSettings'; import { UserInterfaceSettings } from '../UserInterfaceSettings';
import { VisitsSettings } from '../VisitsSettings'; import { VisitsSettings } from '../VisitsSettings';
import { TagsSettings } from '../TagsSettings';
import { ShortUrlsListSettings } from '../ShortUrlsListSettings';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components // Components
bottle.serviceFactory( bottle.serviceFactory(
'Settings', 'Settings',
@ -63,5 +63,3 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('setVisitsSettings', () => setVisitsSettings); bottle.serviceFactory('setVisitsSettings', () => setVisitsSettings);
bottle.serviceFactory('setTagsSettings', () => setTagsSettings); bottle.serviceFactory('setTagsSettings', () => setTagsSettings);
}; };
export default provideServices;

View file

@ -1,10 +1,11 @@
import { FC, useMemo } from 'react'; import type { FC } from 'react';
import { SelectedServer } from '../servers/data'; import { useMemo } from 'react';
import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings'; import type { SelectedServer } from '../servers/data';
import { ShortUrlData } from './data'; import type { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings';
import { ShortUrlCreation } from './reducers/shortUrlCreation'; import type { ShortUrlData } from './data';
import { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult'; import type { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
import { ShortUrlFormProps } from './ShortUrlForm'; import type { ShortUrlCreation } from './reducers/shortUrlCreation';
import type { ShortUrlFormProps } from './ShortUrlForm';
export interface CreateShortUrlProps { export interface CreateShortUrlProps {
basicMode?: boolean; basicMode?: boolean;

View file

@ -1,21 +1,22 @@
import { FC, useEffect, useMemo } from 'react';
import { Button, Card } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { useEffect, useMemo } from 'react';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { SelectedServer } from '../servers/data'; import { Button, Card } from 'reactstrap';
import { Settings } from '../settings/reducers/settings'; import { ShlinkApiError } from '../api/ShlinkApiError';
import { ShortUrlIdentifier } from './data'; import type { SelectedServer } from '../servers/data';
import type { Settings } from '../settings/reducers/settings';
import { useGoBack } from '../utils/helpers/hooks';
import { parseQuery } from '../utils/helpers/query'; import { parseQuery } from '../utils/helpers/query';
import { Message } from '../utils/Message'; import { Message } from '../utils/Message';
import { Result } from '../utils/Result'; import { Result } from '../utils/Result';
import { ShlinkApiError } from '../api/ShlinkApiError'; import type { ShortUrlIdentifier } from './data';
import { useGoBack } from '../utils/helpers/hooks';
import { ShortUrlFormProps } from './ShortUrlForm';
import { ShortUrlDetail } from './reducers/shortUrlDetail';
import { EditShortUrl as EditShortUrlInfo, ShortUrlEdition } from './reducers/shortUrlEdition';
import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers'; import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers';
import type { ShortUrlDetail } from './reducers/shortUrlDetail';
import type { EditShortUrl as EditShortUrlInfo, ShortUrlEdition } from './reducers/shortUrlEdition';
import type { ShortUrlFormProps } from './ShortUrlForm';
interface EditShortUrlConnectProps { interface EditShortUrlConnectProps {
settings: Settings; settings: Settings;

View file

@ -1,13 +1,14 @@
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 {
NumberOrEllipsis } from '../utils/helpers/pagination';
import { import {
pageIsEllipsis,
keyForPage, keyForPage,
progressivePagination, pageIsEllipsis,
prettifyPageNumber, prettifyPageNumber,
NumberOrEllipsis, progressivePagination,
} from '../utils/helpers/pagination'; } from '../utils/helpers/pagination';
import { ShlinkPaginator } from '../api/types';
interface PaginatorProps { interface PaginatorProps {
paginator?: ShlinkPaginator; paginator?: ShlinkPaginator;

View file

@ -1,20 +1,23 @@
import { FC, useEffect, useState } from 'react';
import { InputType } from 'reactstrap/types/lib/Input';
import { Button, FormGroup, Input, Row } from 'reactstrap';
import { cond, isEmpty, pipe, replace, trim, T } from 'ramda';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import { DateTimeInput, DateTimeInputProps } from '../utils/dates/DateTimeInput'; import { cond, isEmpty, pipe, replace, T, trim } from 'ramda';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { Button, FormGroup, Input, Row } from 'reactstrap';
import type { InputType } from 'reactstrap/types/lib/Input';
import type { DomainSelectorProps } from '../domains/DomainSelector';
import type { SelectedServer } from '../servers/data';
import type { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { Checkbox } from '../utils/Checkbox';
import type { DateTimeInputProps } from '../utils/dates/DateTimeInput';
import { DateTimeInput } from '../utils/dates/DateTimeInput';
import { formatIsoDate } from '../utils/helpers/date';
import { supportsForwardQuery } from '../utils/helpers/features'; import { supportsForwardQuery } from '../utils/helpers/features';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';
import { handleEventPreventingDefault, hasValue, OptionalString } from '../utils/utils'; import type { OptionalString } from '../utils/utils';
import { Checkbox } from '../utils/Checkbox'; import { handleEventPreventingDefault, hasValue } from '../utils/utils';
import { SelectedServer } from '../servers/data'; import type { ShortUrlData } from './data';
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { DomainSelectorProps } from '../domains/DomainSelector';
import { formatIsoDate } from '../utils/helpers/date';
import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon';
import { ShortUrlData } from './data';
import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup'; import { ShortUrlFormCheckboxGroup } from './helpers/ShortUrlFormCheckboxGroup';
import { UseExistingIfFoundInfoIcon } from './UseExistingIfFoundInfoIcon';
import './ShortUrlForm.scss'; import './ShortUrlForm.scss';
export type Mode = 'create' | 'create-basic' | 'edit'; export type Mode = 'create' | 'create-basic' | 'edit';

View file

@ -1,23 +1,25 @@
import { FC } from 'react';
import { isEmpty, pipe } from 'ramda';
import { Button, InputGroup, Row, UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTag, faTags } from '@fortawesome/free-solid-svg-icons'; import { faTag, faTags } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames'; import classNames from 'classnames';
import { SearchField } from '../utils/SearchField'; import { isEmpty, pipe } from 'ramda';
import type { FC } from 'react';
import { Button, InputGroup, Row, UncontrolledTooltip } from 'reactstrap';
import type { SelectedServer } from '../servers/data';
import type { Settings } from '../settings/reducers/settings';
import type { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
import { formatIsoDate } from '../utils/helpers/date'; import { formatIsoDate } from '../utils/helpers/date';
import { DateRange, datesToDateRange } from '../utils/helpers/dateIntervals'; import type { DateRange } from '../utils/helpers/dateIntervals';
import { datesToDateRange } from '../utils/helpers/dateIntervals';
import { supportsAllTagsFiltering, supportsFilterDisabledUrls } from '../utils/helpers/features'; import { supportsAllTagsFiltering, supportsFilterDisabledUrls } from '../utils/helpers/features';
import { SelectedServer } from '../servers/data'; import type { OrderDir } from '../utils/helpers/ordering';
import { OrderDir } from '../utils/helpers/ordering';
import { OrderingDropdown } from '../utils/OrderingDropdown'; import { OrderingDropdown } from '../utils/OrderingDropdown';
import { SearchField } from '../utils/SearchField';
import type { ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
import { SHORT_URLS_ORDERABLE_FIELDS } from './data';
import type { ExportShortUrlsBtnProps } from './helpers/ExportShortUrlsBtn';
import { useShortUrlsQuery } from './helpers/hooks'; import { useShortUrlsQuery } from './helpers/hooks';
import { SHORT_URLS_ORDERABLE_FIELDS, ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
import { ExportShortUrlsBtnProps } from './helpers/ExportShortUrlsBtn';
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
import { ShortUrlsFilterDropdown } from './helpers/ShortUrlsFilterDropdown'; import { ShortUrlsFilterDropdown } from './helpers/ShortUrlsFilterDropdown';
import { Settings } from '../settings/reducers/settings';
import './ShortUrlsFilteringBar.scss'; import './ShortUrlsFilteringBar.scss';
interface ShortUrlsFilteringProps { interface ShortUrlsFilteringProps {

View file

@ -1,21 +1,24 @@
import { pipe } from 'ramda'; import { pipe } from 'ramda';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Card } from 'reactstrap';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { determineOrderDir, OrderDir } from '../utils/helpers/ordering'; import { Card } from 'reactstrap';
import { getServerId, SelectedServer } from '../servers/data'; import type { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../api/types';
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 { TableOrderIcon } from '../utils/table/TableOrderIcon'; import type { SelectedServer } from '../servers/data';
import { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../api/types'; import { getServerId } from '../servers/data';
import { DEFAULT_SHORT_URLS_ORDERING, Settings } from '../settings/reducers/settings'; import type { Settings } from '../settings/reducers/settings';
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; import { DEFAULT_SHORT_URLS_ORDERING } from '../settings/reducers/settings';
import { ShortUrlsTableType } from './ShortUrlsTable';
import { Paginator } from './Paginator';
import { useShortUrlsQuery } from './helpers/hooks';
import { ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
import { ShortUrlsFilteringBarType } from './ShortUrlsFilteringBar';
import { supportsExcludeBotsOnShortUrls } from '../utils/helpers/features'; import { supportsExcludeBotsOnShortUrls } from '../utils/helpers/features';
import type { OrderDir } from '../utils/helpers/ordering';
import { determineOrderDir } from '../utils/helpers/ordering';
import { TableOrderIcon } from '../utils/table/TableOrderIcon';
import type { ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
import { useShortUrlsQuery } from './helpers/hooks';
import { Paginator } from './Paginator';
import type { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
import type { ShortUrlsFilteringBarType } from './ShortUrlsFilteringBar';
import type { ShortUrlsTableType } from './ShortUrlsTable';
interface ShortUrlsListProps { interface ShortUrlsListProps {
selectedServer: SelectedServer; selectedServer: SelectedServer;

View file

@ -1,10 +1,10 @@
import { ReactNode } from 'react';
import { isEmpty } from 'ramda';
import classNames from 'classnames'; import classNames from 'classnames';
import { SelectedServer } from '../servers/data'; import { isEmpty } from 'ramda';
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; import type { ReactNode } from 'react';
import { ShortUrlsRowType } from './helpers/ShortUrlsRow'; import type { SelectedServer } from '../servers/data';
import { ShortUrlsOrderableFields } from './data'; import type { ShortUrlsOrderableFields } from './data';
import type { ShortUrlsRowType } from './helpers/ShortUrlsRow';
import type { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
import './ShortUrlsTable.scss'; import './ShortUrlsTable.scss';
interface ShortUrlsTableProps { interface ShortUrlsTableProps {

View file

@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Modal, ModalBody, ModalHeader } from 'reactstrap'; import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import './UseExistingIfFoundInfoIcon.scss'; import './UseExistingIfFoundInfoIcon.scss';

View file

@ -1,5 +1,5 @@
import { Nullable, OptionalString } from '../../utils/utils'; import type { Order } from '../../utils/helpers/ordering';
import { Order } from '../../utils/helpers/ordering'; import type { Nullable, OptionalString } from '../../utils/utils';
export interface EditShortUrlData { export interface EditShortUrlData {
longUrl?: string; longUrl?: string;

View file

@ -4,11 +4,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useEffect } from 'react'; import { useEffect } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard'; import CopyToClipboard from 'react-copy-to-clipboard';
import { Tooltip } from 'reactstrap'; import { Tooltip } from 'reactstrap';
import { ShortUrlCreation } from '../reducers/shortUrlCreation';
import { TimeoutToggle } from '../../utils/helpers/hooks';
import { Result } from '../../utils/Result';
import './CreateShortUrlResult.scss';
import { ShlinkApiError } from '../../api/ShlinkApiError'; import { ShlinkApiError } from '../../api/ShlinkApiError';
import type { TimeoutToggle } from '../../utils/helpers/hooks';
import { Result } from '../../utils/Result';
import type { ShortUrlCreation } from '../reducers/shortUrlCreation';
import './CreateShortUrlResult.scss';
export interface CreateShortUrlResultProps { export interface CreateShortUrlResultProps {
creation: ShortUrlCreation; creation: ShortUrlCreation;

View file

@ -1,12 +1,12 @@
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 { pipe } from 'ramda';
import { ShortUrlDeletion } from '../reducers/shortUrlDeletion';
import { ShortUrlIdentifier, ShortUrlModalProps } from '../data';
import { handleEventPreventingDefault } from '../../utils/utils';
import { Result } from '../../utils/Result';
import { isInvalidDeletionError } from '../../api/utils';
import { ShlinkApiError } from '../../api/ShlinkApiError'; import { ShlinkApiError } from '../../api/ShlinkApiError';
import { isInvalidDeletionError } from '../../api/utils';
import { Result } from '../../utils/Result';
import { handleEventPreventingDefault } from '../../utils/utils';
import type { ShortUrlIdentifier, ShortUrlModalProps } from '../data';
import type { ShortUrlDeletion } from '../reducers/shortUrlDeletion';
interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps { interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps {
shortUrlDeletion: ShortUrlDeletion; shortUrlDeletion: ShortUrlDeletion;

View file

@ -1,10 +1,11 @@
import { FC } from 'react'; import type { FC } from 'react';
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import type { ReportExporter } from '../../common/services/ReportExporter';
import type { SelectedServer } 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 { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import type { ShortUrl } from '../data';
import { isServerWithId, SelectedServer } from '../../servers/data';
import { ShortUrl } from '../data';
import { ReportExporter } from '../../common/services/ReportExporter';
import { useShortUrlsQuery } from './hooks'; import { useShortUrlsQuery } from './hooks';
export interface ExportShortUrlsBtnProps { export interface ExportShortUrlsBtnProps {

View file

@ -1,16 +1,17 @@
import { useMemo, useState } from 'react';
import { Modal, FormGroup, ModalBody, ModalHeader, Row, Button } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFileDownload as downloadIcon } from '@fortawesome/free-solid-svg-icons'; import { faFileDownload as downloadIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useMemo, useState } from 'react';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { ShortUrlModalProps } from '../data'; import { Button, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstrap';
import { SelectedServer } from '../../servers/data'; import type { ImageDownloader } from '../../common/services/ImageDownloader';
import type { SelectedServer } from '../../servers/data';
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
import { buildQrCodeUrl, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
import { supportsNonRestCors } from '../../utils/helpers/features'; import { supportsNonRestCors } from '../../utils/helpers/features';
import { ImageDownloader } from '../../common/services/ImageDownloader'; import type { QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
import { QrFormatDropdown } from './qr-codes/QrFormatDropdown'; import { buildQrCodeUrl } from '../../utils/helpers/qrCodes';
import type { ShortUrlModalProps } from '../data';
import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown'; import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown';
import { QrFormatDropdown } from './qr-codes/QrFormatDropdown';
import './QrCodeModal.scss'; import './QrCodeModal.scss';
interface QrCodeModalConnectProps extends ShortUrlModalProps { interface QrCodeModalConnectProps extends ShortUrlModalProps {

View file

@ -1,7 +1,8 @@
import { FC } from 'react'; import type { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data'; import type { SelectedServer, ServerWithId } from '../../servers/data';
import { ShortUrl } from '../data'; import { isServerWithId } from '../../servers/data';
import type { ShortUrl } from '../data';
import { urlEncodeShortCode } from './index'; import { urlEncodeShortCode } from './index';
export type LinkSuffix = 'visits' | 'edit'; export type LinkSuffix = 'visits' | 'edit';

View file

@ -1,4 +1,4 @@
import { ChangeEvent, FC, PropsWithChildren } from 'react'; import type { ChangeEvent, FC, PropsWithChildren } from 'react';
import { Checkbox } from '../../utils/Checkbox'; import { Checkbox } from '../../utils/Checkbox';
import { InfoTooltip } from '../../utils/InfoTooltip'; import { InfoTooltip } from '../../utils/InfoTooltip';

Some files were not shown because too many files have changed in this diff Show more