mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-03-14 02:08:41 +03:00
commit
2e438f9814
15 changed files with 2674 additions and 1740 deletions
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
@ -12,11 +12,12 @@ updates:
|
|||
fontawesome:
|
||||
patterns:
|
||||
- '@fortawesome/*'
|
||||
eslint-plugins: # TODO Add eslint back once updated to v9
|
||||
eslint:
|
||||
patterns:
|
||||
- '@shlinkio/eslint-config-js-coding-standard'
|
||||
- 'typescript-eslint'
|
||||
- '*eslint-plugin*'
|
||||
- 'eslint'
|
||||
shlink:
|
||||
patterns:
|
||||
- '@shlinkio/*'
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -4,6 +4,24 @@ 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).
|
||||
|
||||
## [4.2.2] - 2024-10-19
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* Update to `@shlinkio/shlink-frontend-kit` 0.6.0
|
||||
* Update to `@shlinkio/shlink-web-component` 0.10.1
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* *Nothing*
|
||||
|
||||
### Fixed
|
||||
* [shlink-web-component#475](https://github.com/shlinkio/shlink-web-component/issues/475) Fix incorrect amount of dots being displayed in line charts when the difference in days/weeks/months is rounded up.
|
||||
|
||||
|
||||
## [4.2.1] - 2024-10-09
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:22.9-alpine as node
|
||||
FROM node:23.0-alpine as node
|
||||
COPY . /shlink-web-client
|
||||
ARG VERSION="latest"
|
||||
ENV VERSION ${VERSION}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
[](https://github.com/shlinkio/shlink-web-client/blob/main/LICENSE)
|
||||
|
||||
[](https://fosstodon.org/@shlinkio)
|
||||
[](https://twitter.com/shlinkio)
|
||||
[](https://bsky.app/profile/shlinkio.bsky.social)
|
||||
[](https://slnk.to/donate)
|
||||
|
||||
|
|
4292
package-lock.json
generated
4292
package-lock.json
generated
File diff suppressed because it is too large
Load diff
28
package.json
28
package.json
|
@ -31,11 +31,11 @@
|
|||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@json2csv/plainjs": "^7.0.6",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"@shlinkio/data-manipulation": "^1.0.3",
|
||||
"@shlinkio/shlink-frontend-kit": "^0.5.2",
|
||||
"@shlinkio/shlink-frontend-kit": "^0.6.0",
|
||||
"@shlinkio/shlink-js-sdk": "^1.2.0",
|
||||
"@shlinkio/shlink-web-component": "^0.8.1",
|
||||
"@shlinkio/shlink-web-component": "^0.10.1",
|
||||
"bootstrap": "5.2.3",
|
||||
"bottlejs": "^2.0.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-external-link": "^2.3.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"reactstrap": "^9.2.3",
|
||||
"redux-localstorage-simple": "^2.5.1",
|
||||
"uuid": "^10.0.0",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"workbox-strategies": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shlinkio/eslint-config-js-coding-standard": "~3.1.0",
|
||||
"@shlinkio/eslint-config-js-coding-standard": "~3.2.0",
|
||||
"@shlinkio/stylelint-config-css-coding-standard": "~1.1.1",
|
||||
"@stylistic/eslint-plugin": "^2.9.0",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
|
@ -65,25 +65,25 @@
|
|||
"@testing-library/user-event": "^14.5.2",
|
||||
"@total-typescript/shoehorn": "^0.1.2",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"@vitest/coverage-v8": "^2.1.2",
|
||||
"@vitest/coverage-v8": "^2.1.3",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axe-core": "^4.10.0",
|
||||
"axe-core": "^4.10.1",
|
||||
"chalk": "^5.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.0",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"history": "^5.3.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"sass": "^1.79.4",
|
||||
"sass": "^1.80.3",
|
||||
"stylelint": "^15.11.0",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"vite": "^5.4.8",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"vite": "^5.4.9",
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vitest": "^2.0.2"
|
||||
},
|
||||
|
|
|
@ -20,7 +20,6 @@ export function componentFactory<Deps, CompType = Omit<Partial<Deps>, keyof FC>>
|
|||
console.error(`[Debug] Could not find "${dep as string}" dependency in container`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
Component[dep] = resolvedDependency;
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import './index.scss';
|
|||
const store = setUpStore(container);
|
||||
const { App, ScrollToTop, ErrorHandler, appUpdateAvailable } = container;
|
||||
|
||||
createRoot(document.getElementById('root')!).render( // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter basename={pack.homepage}>
|
||||
<ErrorHandler>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { TimeoutToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import { Result, useToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from 'reactstrap';
|
||||
import { NoMenuLayout } from '../common/NoMenuLayout';
|
||||
|
@ -50,26 +50,23 @@ const CreateServer: FCWithDeps<CreateServerProps, CreateServerDeps> = ({ servers
|
|||
createServers([{ ...theServerData, id }]);
|
||||
navigate(`/server/${id}`);
|
||||
}, [createServers, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!serverData) {
|
||||
return;
|
||||
}
|
||||
const onSubmit = useCallback((newServerData: ServerData) => {
|
||||
setServerData(newServerData);
|
||||
|
||||
const serverExists = Object.values(servers).some(
|
||||
({ url, apiKey }) => serverData?.url === url && serverData?.apiKey === apiKey,
|
||||
({ url, apiKey }) => newServerData.url === url && newServerData.apiKey === apiKey,
|
||||
);
|
||||
|
||||
if (serverExists) {
|
||||
toggleConfirmModal();
|
||||
} else {
|
||||
saveNewServer(serverData);
|
||||
saveNewServer(newServerData);
|
||||
}
|
||||
}, [saveNewServer, serverData, servers, toggleConfirmModal]);
|
||||
}, [saveNewServer, servers, toggleConfirmModal]);
|
||||
|
||||
return (
|
||||
<NoMenuLayout>
|
||||
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={setServerData}>
|
||||
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={onSubmit}>
|
||||
{!hasServers && (
|
||||
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />
|
||||
)}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||
import type { TimeoutToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import { Result, SearchField, SimpleCard } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, Row } from 'reactstrap';
|
||||
import { NoMenuLayout } from '../common/NoMenuLayout';
|
||||
|
@ -34,26 +34,23 @@ const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ serv
|
|||
useTimeoutToggle,
|
||||
ManageServersRow,
|
||||
} = useDependencies(ManageServers);
|
||||
const allServers = Object.values(servers);
|
||||
const [serversList, setServersList] = useState(allServers);
|
||||
const filterServers = (searchTerm: string) => setServersList(
|
||||
allServers.filter(({ name, url }) => `${name} ${url}`.toLowerCase().match(searchTerm.toLowerCase())),
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const allServers = useMemo(() => Object.values(servers), [servers]);
|
||||
const filteredServers = useMemo(
|
||||
() => allServers.filter(({ name, url }) => `${name} ${url}`.toLowerCase().match(searchTerm.toLowerCase())),
|
||||
[allServers, searchTerm],
|
||||
);
|
||||
const hasAutoConnect = serversList.some(({ autoConnect }) => !!autoConnect);
|
||||
const hasAutoConnect = allServers.some(({ autoConnect }) => !!autoConnect);
|
||||
const [errorImporting, setErrorImporting] = useTimeoutToggle(false, SHOW_IMPORT_MSG_TIME);
|
||||
|
||||
useEffect(() => {
|
||||
setServersList(Object.values(servers));
|
||||
}, [servers]);
|
||||
|
||||
return (
|
||||
<NoMenuLayout>
|
||||
<SearchField className="mb-3" onChange={filterServers} />
|
||||
<SearchField className="mb-3" onChange={setSearchTerm} />
|
||||
|
||||
<Row className="mb-3">
|
||||
<div className="col-md-6 d-flex d-md-block mb-2 mb-md-0">
|
||||
<ImportServersBtn className="flex-fill" onImportError={setErrorImporting}>Import servers</ImportServersBtn>
|
||||
{allServers.length > 0 && (
|
||||
{filteredServers.length > 0 && (
|
||||
<Button outline className="ms-2 flex-fill" onClick={async () => serversExporter.exportServers()}>
|
||||
<FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers
|
||||
</Button>
|
||||
|
@ -77,8 +74,8 @@ const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ serv
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!serversList.length && <tr className="text-center"><td colSpan={4}>No servers found.</td></tr>}
|
||||
{serversList.map((server) => (
|
||||
{!filteredServers.length && <tr className="text-center"><td colSpan={4}>No servers found.</td></tr>}
|
||||
{filteredServers.map((server) => (
|
||||
<ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} />
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
@ -67,7 +67,7 @@ const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBt
|
|||
})
|
||||
.then(() => {
|
||||
// Reset input after processing file
|
||||
(target as { value: string | null }).value = null; // eslint-disable-line no-param-reassign
|
||||
(target as { value: string | null }).value = null;
|
||||
})
|
||||
.catch(onImportError),
|
||||
[create, onImportError, servers, serversImporter, showModal],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { InputFormGroup, SimpleCard } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC, PropsWithChildren, ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { handleEventPreventingDefault } from '../../utils/utils';
|
||||
import type { ServerData } from '../data';
|
||||
|
||||
|
@ -11,19 +11,11 @@ type ServerFormProps = PropsWithChildren<{
|
|||
}>;
|
||||
|
||||
export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, children, title }) => {
|
||||
const [name, setName] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [name, setName] = useState(initialValues?.name ?? '');
|
||||
const [url, setUrl] = useState(initialValues?.url ?? '');
|
||||
const [apiKey, setApiKey] = useState(initialValues?.apiKey ?? '');
|
||||
const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey }));
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValues) {
|
||||
setName(initialValues.name);
|
||||
setUrl(initialValues.url);
|
||||
setApiKey(initialValues.apiKey);
|
||||
}
|
||||
}, [initialValues]);
|
||||
|
||||
return (
|
||||
<form className="server-form" name="serverForm" onSubmit={handleSubmit}>
|
||||
<SimpleCard className="mb-3" title={title}>
|
||||
|
|
|
@ -22,7 +22,7 @@ export class ServersExporter {
|
|||
saveCsv(this.window, csv, SERVERS_FILENAME);
|
||||
} catch (e) {
|
||||
// FIXME Handle error
|
||||
console.error(e); // eslint-disable-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { ShlinkState } from '../../container/types';
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
export const migrateDeprecatedSettings = (state: Partial<ShlinkState>): Partial<ShlinkState> => {
|
||||
if (!state.settings) {
|
||||
return state;
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('ShlinkApiClientBuilder', () => {
|
|||
const apiKey = 'apiKey';
|
||||
const apiClient = buildShlinkApiClient(fromPartial({}))(server({ url, apiKey }));
|
||||
|
||||
expect(apiClient['serverInfo'].baseUrl).toEqual(url); // eslint-disable-line @typescript-eslint/dot-notation
|
||||
expect(apiClient['serverInfo'].apiKey).toEqual(apiKey); // eslint-disable-line @typescript-eslint/dot-notation
|
||||
expect(apiClient['serverInfo'].baseUrl).toEqual(url);
|
||||
expect(apiClient['serverInfo'].apiKey).toEqual(apiKey);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue