mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-02-27 20:26:40 +00:00
Do not inject servers state or actions
This commit is contained in:
@@ -5,10 +5,13 @@ import type { FC } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Route, Routes, useLocation } from 'react-router';
|
||||
import { AppUpdateBanner } from '../common/AppUpdateBanner';
|
||||
import { MainHeader } from '../common/MainHeader';
|
||||
import { NotFound } from '../common/NotFound';
|
||||
import { ShlinkVersionsContainer } from '../common/ShlinkVersionsContainer';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import type { ServersMap } from '../servers/data';
|
||||
import { EditServer } from '../servers/EditServer';
|
||||
import { Settings } from '../settings/Settings';
|
||||
import { forceUpdate } from '../utils/helpers/sw';
|
||||
|
||||
@@ -21,26 +24,20 @@ type AppProps = {
|
||||
};
|
||||
|
||||
type AppDeps = {
|
||||
MainHeader: FC;
|
||||
Home: FC;
|
||||
ShlinkWebComponentContainer: FC;
|
||||
CreateServer: FC;
|
||||
EditServer: FC;
|
||||
ManageServers: FC;
|
||||
ShlinkVersionsContainer: FC;
|
||||
};
|
||||
|
||||
const App: FCWithDeps<AppProps, AppDeps> = (
|
||||
{ fetchServers, servers, settings, appUpdated, resetAppUpdate },
|
||||
) => {
|
||||
const {
|
||||
MainHeader,
|
||||
Home,
|
||||
ShlinkWebComponentContainer,
|
||||
CreateServer,
|
||||
EditServer,
|
||||
ManageServers,
|
||||
ShlinkVersionsContainer,
|
||||
} = useDependencies(App);
|
||||
|
||||
const location = useLocation();
|
||||
@@ -99,11 +96,8 @@ const App: FCWithDeps<AppProps, AppDeps> = (
|
||||
};
|
||||
|
||||
export const AppFactory = componentFactory(App, [
|
||||
'MainHeader',
|
||||
'Home',
|
||||
'ShlinkWebComponentContainer',
|
||||
'CreateServer',
|
||||
'EditServer',
|
||||
'ManageServers',
|
||||
'ShlinkVersionsContainer',
|
||||
]);
|
||||
|
||||
@@ -2,19 +2,17 @@ import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Button, Card } from '@shlinkio/shlink-frontend-kit';
|
||||
import { clsx } from 'clsx';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import { useNavigate } from 'react-router';
|
||||
import type { ServersMap } from '../servers/data';
|
||||
import { useServers } from '../servers/reducers/servers';
|
||||
import { ServersListGroup } from '../servers/ServersListGroup';
|
||||
import { ShlinkLogo } from './img/ShlinkLogo';
|
||||
|
||||
export type HomeProps = {
|
||||
servers: ServersMap;
|
||||
};
|
||||
|
||||
export const Home = ({ servers }: HomeProps) => {
|
||||
export const Home: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { servers } = useServers();
|
||||
const serversList = Object.values(servers);
|
||||
const hasServers = serversList.length > 0;
|
||||
|
||||
|
||||
@@ -3,16 +3,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { NavBar } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import { ServersDropdown } from '../servers/ServersDropdown';
|
||||
import { ShlinkLogo } from './img/ShlinkLogo';
|
||||
|
||||
type MainHeaderDeps = {
|
||||
ServersDropdown: FC;
|
||||
};
|
||||
|
||||
const MainHeader: FCWithDeps<unknown, MainHeaderDeps> = () => {
|
||||
const { ServersDropdown } = useDependencies(MainHeader);
|
||||
export const MainHeader: FC = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const settingsPath = '/settings';
|
||||
@@ -37,5 +31,3 @@ const MainHeader: FCWithDeps<unknown, MainHeaderDeps> = () => {
|
||||
</NavBar>
|
||||
);
|
||||
};
|
||||
|
||||
export const MainHeaderFactory = componentFactory(MainHeader, ['ServersDropdown']);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { memo } from 'react';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import { isReachableServer } from '../servers/data';
|
||||
import { ServerError } from '../servers/helpers/ServerError';
|
||||
import type { WithSelectedServerPropsDeps } from '../servers/helpers/withSelectedServer';
|
||||
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
||||
import { useSelectedServer } from '../servers/reducers/selectedServer';
|
||||
@@ -33,7 +34,6 @@ const ShlinkWebComponentContainer: FCWithDeps<
|
||||
const {
|
||||
buildShlinkApiClient,
|
||||
TagColorsStorage: tagColorsStorage,
|
||||
ServerError,
|
||||
} = useDependencies(ShlinkWebComponentContainer);
|
||||
const { selectedServer } = useSelectedServer();
|
||||
|
||||
@@ -63,5 +63,4 @@ const ShlinkWebComponentContainer: FCWithDeps<
|
||||
export const ShlinkWebComponentContainerFactory = componentFactory(ShlinkWebComponentContainer, [
|
||||
'buildShlinkApiClient',
|
||||
'TagColorsStorage',
|
||||
'ServerError',
|
||||
]);
|
||||
|
||||
@@ -4,9 +4,7 @@ import type { ConnectDecorator } from '../../container/types';
|
||||
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
||||
import { ErrorHandler } from '../ErrorHandler';
|
||||
import { Home } from '../Home';
|
||||
import { MainHeaderFactory } from '../MainHeader';
|
||||
import { ScrollToTop } from '../ScrollToTop';
|
||||
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
|
||||
import { ShlinkWebComponentContainerFactory } from '../ShlinkWebComponentContainer';
|
||||
|
||||
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
@@ -19,16 +17,11 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
// Components
|
||||
bottle.serviceFactory('ScrollToTop', () => ScrollToTop);
|
||||
|
||||
bottle.factory('MainHeader', MainHeaderFactory);
|
||||
|
||||
bottle.serviceFactory('Home', () => Home);
|
||||
bottle.decorator('Home', withoutSelectedServer);
|
||||
bottle.decorator('Home', connect(['servers'], []));
|
||||
|
||||
bottle.factory('ShlinkWebComponentContainer', ShlinkWebComponentContainerFactory);
|
||||
bottle.decorator('ShlinkWebComponentContainer', connect(['settings'], ['selectServer']));
|
||||
|
||||
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
|
||||
bottle.decorator('ShlinkWebComponentContainer', connect(['settings'], []));
|
||||
|
||||
bottle.serviceFactory('ErrorHandler', () => ErrorHandler);
|
||||
};
|
||||
|
||||
@@ -36,5 +36,5 @@ const connect: ConnectDecorator = (propsFromState: string[] | null, actionServic
|
||||
provideAppServices(bottle, connect);
|
||||
provideCommonServices(bottle, connect);
|
||||
provideApiServices(bottle);
|
||||
provideServersServices(bottle, connect);
|
||||
provideServersServices(bottle);
|
||||
provideUtilsServices(bottle);
|
||||
|
||||
@@ -7,19 +7,15 @@ import { NoMenuLayout } from '../common/NoMenuLayout';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import { useGoBack } from '../utils/helpers/hooks';
|
||||
import type { ServerData, ServersMap, ServerWithId } from './data';
|
||||
import type { ServerData } from './data';
|
||||
import { ensureUniqueIds } from './helpers';
|
||||
import { DuplicatedServersModal } from './helpers/DuplicatedServersModal';
|
||||
import type { ImportServersBtnProps } from './helpers/ImportServersBtn';
|
||||
import { ServerForm } from './helpers/ServerForm';
|
||||
import { useServers } from './reducers/servers';
|
||||
|
||||
const SHOW_IMPORT_MSG_TIME = 4000;
|
||||
|
||||
type CreateServerProps = {
|
||||
createServers: (servers: ServerWithId[]) => void;
|
||||
servers: ServersMap;
|
||||
};
|
||||
|
||||
type CreateServerDeps = {
|
||||
ImportServersBtn: FC<ImportServersBtnProps>;
|
||||
useTimeoutToggle: TimeoutToggle;
|
||||
@@ -34,7 +30,8 @@ const ImportResult = ({ variant }: Pick<ResultProps, 'variant'>) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const CreateServer: FCWithDeps<CreateServerProps, CreateServerDeps> = ({ servers, createServers }) => {
|
||||
const CreateServer: FCWithDeps<any, CreateServerDeps> = () => {
|
||||
const { servers, createServers } = useServers();
|
||||
const { ImportServersBtn, useTimeoutToggle } = useDependencies(CreateServer);
|
||||
const navigate = useNavigate();
|
||||
const goBack = useGoBack();
|
||||
|
||||
@@ -2,21 +2,14 @@ import { useToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import type { ServerWithId } from './data';
|
||||
import type { DeleteServerModalProps } from './DeleteServerModal';
|
||||
import { DeleteServerModal } from './DeleteServerModal';
|
||||
|
||||
export type DeleteServerButtonProps = PropsWithChildren<{
|
||||
server: ServerWithId;
|
||||
}>;
|
||||
|
||||
type DeleteServerButtonDeps = {
|
||||
DeleteServerModal: FC<DeleteServerModalProps>;
|
||||
};
|
||||
|
||||
const DeleteServerButton: FCWithDeps<DeleteServerButtonProps, DeleteServerButtonDeps> = ({ server, children }) => {
|
||||
const { DeleteServerModal } = useDependencies(DeleteServerButton);
|
||||
export const DeleteServerButton: FC<DeleteServerButtonProps> = ({ server, children }) => {
|
||||
const { flag: isModalOpen, setToTrue: showModal, setToFalse: hideModal } = useToggle();
|
||||
const navigate = useNavigate();
|
||||
const onClose = useCallback((confirmed: boolean) => {
|
||||
@@ -35,5 +28,3 @@ const DeleteServerButton: FCWithDeps<DeleteServerButtonProps, DeleteServerButton
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DeleteServerButtonFactory = componentFactory(DeleteServerButton, ['DeleteServerModal']);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CardModal } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import type { ServerWithId } from './data';
|
||||
import { useServers } from './reducers/servers';
|
||||
|
||||
export type DeleteServerModalProps = {
|
||||
server: ServerWithId;
|
||||
@@ -10,11 +11,8 @@ export type DeleteServerModalProps = {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
type DeleteServerModalConnectProps = DeleteServerModalProps & {
|
||||
deleteServer: (server: ServerWithId) => void;
|
||||
};
|
||||
|
||||
export const DeleteServerModal: FC<DeleteServerModalConnectProps> = ({ server, onClose, open, deleteServer }) => {
|
||||
export const DeleteServerModal: FC<DeleteServerModalProps> = ({ server, onClose, open }) => {
|
||||
const { deleteServer } = useServers();
|
||||
const onClosed = useCallback((exitAction: ExitAction) => {
|
||||
if (exitAction === 'confirm') {
|
||||
deleteServer(server);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button, useParsedQuery } from '@shlinkio/shlink-frontend-kit';
|
||||
import { NoMenuLayout } from '../common/NoMenuLayout';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import { useDependencies } from '../container/utils';
|
||||
import { useGoBack } from '../utils/helpers/hooks';
|
||||
import type { ServerData } from './data';
|
||||
import { isServerWithId } from './data';
|
||||
@@ -9,12 +9,10 @@ import { ServerForm } from './helpers/ServerForm';
|
||||
import type { WithSelectedServerPropsDeps } from './helpers/withSelectedServer';
|
||||
import { withSelectedServer } from './helpers/withSelectedServer';
|
||||
import { useSelectedServer } from './reducers/selectedServer';
|
||||
import { useServers } from './reducers/servers';
|
||||
|
||||
type EditServerProps = {
|
||||
editServer: (serverId: string, serverData: ServerData) => void;
|
||||
};
|
||||
|
||||
const EditServer: FCWithDeps<EditServerProps, WithSelectedServerPropsDeps> = withSelectedServer(({ editServer }) => {
|
||||
export const EditServer: FCWithDeps<any, WithSelectedServerPropsDeps> = withSelectedServer(() => {
|
||||
const { editServer } = useServers();
|
||||
const { buildShlinkApiClient } = useDependencies(EditServer);
|
||||
const { selectServer, selectedServer } = useSelectedServer();
|
||||
const goBack = useGoBack();
|
||||
@@ -45,5 +43,3 @@ const EditServer: FCWithDeps<EditServerProps, WithSelectedServerPropsDeps> = wit
|
||||
</NoMenuLayout>
|
||||
);
|
||||
});
|
||||
|
||||
export const EditServerFactory = componentFactory(EditServer, ['ServerError']);
|
||||
|
||||
@@ -7,31 +7,26 @@ import { useMemo, useState } from 'react';
|
||||
import { NoMenuLayout } from '../common/NoMenuLayout';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import type { ServersMap } from './data';
|
||||
import type { ImportServersBtnProps } from './helpers/ImportServersBtn';
|
||||
import type { ManageServersRowProps } from './ManageServersRow';
|
||||
import { ManageServersRow } from './ManageServersRow';
|
||||
import { useServers } from './reducers/servers';
|
||||
import type { ServersExporter } from './services/ServersExporter';
|
||||
|
||||
type ManageServersProps = {
|
||||
servers: ServersMap;
|
||||
};
|
||||
|
||||
type ManageServersDeps = {
|
||||
ServersExporter: ServersExporter;
|
||||
ImportServersBtn: FC<ImportServersBtnProps>;
|
||||
useTimeoutToggle: TimeoutToggle;
|
||||
ManageServersRow: FC<ManageServersRowProps>;
|
||||
};
|
||||
|
||||
const SHOW_IMPORT_MSG_TIME = 4000;
|
||||
|
||||
const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ servers }) => {
|
||||
const ManageServers: FCWithDeps<unknown, ManageServersDeps> = () => {
|
||||
const {
|
||||
ServersExporter: serversExporter,
|
||||
ImportServersBtn,
|
||||
useTimeoutToggle,
|
||||
ManageServersRow,
|
||||
} = useDependencies(ManageServers);
|
||||
const { servers } = useServers();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const allServers = useMemo(() => Object.values(servers), [servers]);
|
||||
const filteredServers = useMemo(
|
||||
@@ -93,5 +88,4 @@ export const ManageServersFactory = componentFactory(ManageServers, [
|
||||
'ServersExporter',
|
||||
'ImportServersBtn',
|
||||
'useTimeoutToggle',
|
||||
'ManageServersRow',
|
||||
]);
|
||||
|
||||
@@ -3,22 +3,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Table, Tooltip, useTooltip } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import type { ServerWithId } from './data';
|
||||
import type { ManageServersRowDropdownProps } from './ManageServersRowDropdown';
|
||||
import { ManageServersRowDropdown } from './ManageServersRowDropdown';
|
||||
|
||||
export type ManageServersRowProps = {
|
||||
server: ServerWithId;
|
||||
hasAutoConnect: boolean;
|
||||
};
|
||||
|
||||
type ManageServersRowDeps = {
|
||||
ManageServersRowDropdown: FC<ManageServersRowDropdownProps>;
|
||||
};
|
||||
|
||||
const ManageServersRow: FCWithDeps<ManageServersRowProps, ManageServersRowDeps> = ({ server, hasAutoConnect }) => {
|
||||
const { ManageServersRowDropdown } = useDependencies(ManageServersRow);
|
||||
export const ManageServersRow: FC<ManageServersRowProps> = ({ server, hasAutoConnect }) => {
|
||||
const { anchor, tooltip } = useTooltip();
|
||||
|
||||
return (
|
||||
@@ -31,6 +24,7 @@ const ManageServersRow: FCWithDeps<ManageServersRowProps, ManageServersRowDeps>
|
||||
icon={checkIcon}
|
||||
className="text-lm-brand dark:text-dm-brand"
|
||||
{...anchor}
|
||||
data-testid="auto-connect"
|
||||
/>
|
||||
<Tooltip {...tooltip}>Auto-connect to this server</Tooltip>
|
||||
</>
|
||||
@@ -47,5 +41,3 @@ const ManageServersRow: FCWithDeps<ManageServersRowProps, ManageServersRowDeps>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
export const ManageServersRowFactory = componentFactory(ManageServersRow, ['ManageServersRowDropdown']);
|
||||
|
||||
@@ -6,29 +6,18 @@ import {
|
||||
faPlug as connectIcon,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { RowDropdown,useToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import { RowDropdown, useToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import type { FCWithDeps } from '../container/utils';
|
||||
import { componentFactory, useDependencies } from '../container/utils';
|
||||
import type { ServerWithId } from './data';
|
||||
import type { DeleteServerModalProps } from './DeleteServerModal';
|
||||
import { DeleteServerModal } from './DeleteServerModal';
|
||||
import { useServers } from './reducers/servers';
|
||||
|
||||
export type ManageServersRowDropdownProps = {
|
||||
server: ServerWithId;
|
||||
};
|
||||
|
||||
type ManageServersRowDropdownConnectProps = ManageServersRowDropdownProps & {
|
||||
setAutoConnect: (server: ServerWithId, autoConnect: boolean) => void;
|
||||
};
|
||||
|
||||
type ManageServersRowDropdownDeps = {
|
||||
DeleteServerModal: FC<DeleteServerModalProps>
|
||||
};
|
||||
|
||||
const ManageServersRowDropdown: FCWithDeps<ManageServersRowDropdownConnectProps, ManageServersRowDropdownDeps> = (
|
||||
{ server, setAutoConnect },
|
||||
) => {
|
||||
const { DeleteServerModal } = useDependencies(ManageServersRowDropdown);
|
||||
export const ManageServersRowDropdown: FC<ManageServersRowDropdownProps> = ({ server }) => {
|
||||
const { setAutoConnect } = useServers();
|
||||
const { flag: isModalOpen, setToTrue: showModal, setToFalse: hideModal } = useToggle();
|
||||
const serverUrl = `/server/${server.id}`;
|
||||
const { autoConnect: isAutoConnect } = server;
|
||||
@@ -56,5 +45,3 @@ const ManageServersRowDropdown: FCWithDeps<ManageServersRowDropdownConnectProps,
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ManageServersRowDropdownFactory = componentFactory(ManageServersRowDropdown, ['DeleteServerModal']);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Dropdown, NavBar } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { ServersMap } from './data';
|
||||
import type { FC } from 'react';
|
||||
import { getServerId } from './data';
|
||||
import { useSelectedServer } from './reducers/selectedServer';
|
||||
import { useServers } from './reducers/servers';
|
||||
|
||||
export interface ServersDropdownProps {
|
||||
servers: ServersMap;
|
||||
}
|
||||
|
||||
export const ServersDropdown = ({ servers }: ServersDropdownProps) => {
|
||||
export const ServersDropdown: FC = () => {
|
||||
const { servers } = useServers();
|
||||
const serversList = Object.values(servers);
|
||||
const { selectedServer } = useSelectedServer();
|
||||
|
||||
|
||||
@@ -26,18 +26,16 @@ const ServerListItem = ({ id, name }: { id: string; name: string }) => (
|
||||
);
|
||||
|
||||
export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, borderless }) => (
|
||||
<>
|
||||
{servers.length > 0 && (
|
||||
<div
|
||||
data-testid="list"
|
||||
className={clsx(
|
||||
'w-full border-lm-border dark:border-dm-border',
|
||||
'md:max-h-56 md:overflow-y-auto -mb-1 scroll-thin',
|
||||
{ 'border-y': !borderless },
|
||||
)}
|
||||
>
|
||||
{servers.map(({ id, name }) => <ServerListItem key={id} id={id} name={name} />)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
servers.length > 0 && (
|
||||
<div
|
||||
data-testid="list"
|
||||
className={clsx(
|
||||
'w-full border-lm-border dark:border-dm-border',
|
||||
'md:max-h-56 md:overflow-y-auto -mb-1 scroll-thin',
|
||||
{ 'border-y': !borderless },
|
||||
)}
|
||||
>
|
||||
{servers.map(({ id, name }) => <ServerListItem key={id} id={id} name={name} />)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = (
|
||||
cancelText={hasMultipleServers ? 'Ignore duplicates' : 'Discard'}
|
||||
>
|
||||
<p>{hasMultipleServers ? 'The next servers already exist:' : 'There is already a server with:'}</p>
|
||||
<ul className="list-disc mt-4">
|
||||
<ul className="list-disc my-4 pl-5">
|
||||
{duplicatedServers.map(({ url, apiKey }, index) => (!hasMultipleServers ? (
|
||||
<Fragment key={index}>
|
||||
<li>URL: <b>{url}</b></li>
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { ChangeEvent, PropsWithChildren } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { FCWithDeps } from '../../container/utils';
|
||||
import { componentFactory, useDependencies } from '../../container/utils';
|
||||
import type { ServerData, ServersMap, ServerWithId } from '../data';
|
||||
import type { ServerData } from '../data';
|
||||
import { useServers } from '../reducers/servers';
|
||||
import type { ServersImporter } from '../services/ServersImporter';
|
||||
import { DuplicatedServersModal } from './DuplicatedServersModal';
|
||||
import { dedupServers, ensureUniqueIds } from './index';
|
||||
@@ -17,24 +18,18 @@ export type ImportServersBtnProps = PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
type ImportServersBtnConnectProps = ImportServersBtnProps & {
|
||||
createServers: (servers: ServerWithId[]) => void;
|
||||
servers: ServersMap;
|
||||
};
|
||||
|
||||
type ImportServersBtnDeps = {
|
||||
ServersImporter: ServersImporter
|
||||
};
|
||||
|
||||
const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBtnDeps> = ({
|
||||
createServers,
|
||||
servers,
|
||||
const ImportServersBtn: FCWithDeps<ImportServersBtnProps, ImportServersBtnDeps> = ({
|
||||
children,
|
||||
onImport,
|
||||
onError = () => {},
|
||||
tooltipPlacement = 'bottom',
|
||||
className = '',
|
||||
}) => {
|
||||
const { createServers, servers } = useServers();
|
||||
const { ServersImporter: serversImporter } = useDependencies(ImportServersBtn);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { anchor, tooltip } = useTooltip({ placement: tooltipPlacement });
|
||||
|
||||
@@ -2,24 +2,14 @@ import { Card, Message } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { NoMenuLayout } from '../../common/NoMenuLayout';
|
||||
import type { FCWithDeps } from '../../container/utils';
|
||||
import { componentFactory, useDependencies } from '../../container/utils';
|
||||
import type { ServersMap } from '../data';
|
||||
import { isServerWithId } from '../data';
|
||||
import type { DeleteServerButtonProps } from '../DeleteServerButton';
|
||||
import { DeleteServerButton } from '../DeleteServerButton';
|
||||
import { useSelectedServer } from '../reducers/selectedServer';
|
||||
import { useServers } from '../reducers/servers';
|
||||
import { ServersListGroup } from '../ServersListGroup';
|
||||
|
||||
type ServerErrorProps = {
|
||||
servers: ServersMap;
|
||||
};
|
||||
|
||||
type ServerErrorDeps = {
|
||||
DeleteServerButton: FC<DeleteServerButtonProps>;
|
||||
};
|
||||
|
||||
const ServerError: FCWithDeps<ServerErrorProps, ServerErrorDeps> = ({ servers }) => {
|
||||
const { DeleteServerButton } = useDependencies(ServerError);
|
||||
export const ServerError: FC = () => {
|
||||
const { servers } = useServers();
|
||||
const { selectedServer } = useSelectedServer();
|
||||
|
||||
return (
|
||||
@@ -55,5 +45,3 @@ const ServerError: FCWithDeps<ServerErrorProps, ServerErrorDeps> = ({ servers })
|
||||
</NoMenuLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServerErrorFactory = componentFactory(ServerError, ['DeleteServerButton']);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Message } from '@shlinkio/shlink-frontend-kit';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||
@@ -8,9 +7,9 @@ import type { FCWithDeps } from '../../container/utils';
|
||||
import { useDependencies } from '../../container/utils';
|
||||
import { isNotFoundServer } from '../data';
|
||||
import { useSelectedServer } from '../reducers/selectedServer';
|
||||
import { ServerError } from './ServerError';
|
||||
|
||||
export type WithSelectedServerPropsDeps = {
|
||||
ServerError: FC;
|
||||
buildShlinkApiClient: ShlinkApiClientBuilder;
|
||||
};
|
||||
|
||||
@@ -18,7 +17,7 @@ export function withSelectedServer<T extends object>(
|
||||
WrappedComponent: FCWithDeps<T, WithSelectedServerPropsDeps>,
|
||||
) {
|
||||
const ComponentWrapper: FCWithDeps<T, WithSelectedServerPropsDeps> = (props) => {
|
||||
const { ServerError, buildShlinkApiClient } = useDependencies(ComponentWrapper);
|
||||
const { buildShlinkApiClient } = useDependencies(ComponentWrapper);
|
||||
const params = useParams<{ serverId: string }>();
|
||||
const { selectServer, selectedServer } = useSelectedServer();
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { useCallback } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../../store';
|
||||
import type { ServerData, ServersMap, ServerWithId } from '../data';
|
||||
import { serversListToMap } from '../helpers';
|
||||
|
||||
interface EditServer {
|
||||
type EditServer = {
|
||||
serverId: string;
|
||||
serverData: Partial<ServerData>;
|
||||
}
|
||||
};
|
||||
|
||||
interface SetAutoConnect {
|
||||
type SetAutoConnect = {
|
||||
serverId: string;
|
||||
autoConnect: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
const initialState: ServersMap = {};
|
||||
|
||||
export const { actions, reducer } = createSlice({
|
||||
export const { actions, reducer: serversReducer } = createSlice({
|
||||
name: 'shlink/servers',
|
||||
initialState,
|
||||
reducers: {
|
||||
@@ -65,4 +67,19 @@ export const { actions, reducer } = createSlice({
|
||||
|
||||
export const { editServer, deleteServer, setAutoConnect, createServers } = actions;
|
||||
|
||||
export const serversReducer = reducer;
|
||||
export const useServers = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const servers = useAppSelector((state) => state.servers);
|
||||
const editServer = useCallback(
|
||||
(serverId: string, serverData: Partial<ServerData>) => dispatch(actions.editServer(serverId, serverData)),
|
||||
[dispatch],
|
||||
);
|
||||
const deleteServer = useCallback((server: ServerWithId) => dispatch(actions.deleteServer(server)), [dispatch]);
|
||||
const setAutoConnect = useCallback(
|
||||
(serverData: ServerWithId, autoConnect: boolean) => dispatch(actions.setAutoConnect(serverData, autoConnect)),
|
||||
[dispatch],
|
||||
);
|
||||
const createServers = useCallback((servers: ServerWithId[]) => dispatch(actions.createServers(servers)), [dispatch]);
|
||||
|
||||
return { servers, editServer, deleteServer, setAutoConnect, createServers };
|
||||
};
|
||||
|
||||
@@ -1,63 +1,26 @@
|
||||
import type Bottle from 'bottlejs';
|
||||
import type { ConnectDecorator } from '../../container/types';
|
||||
import { CreateServerFactory } from '../CreateServer';
|
||||
import { DeleteServerButtonFactory } from '../DeleteServerButton';
|
||||
import { DeleteServerModal } from '../DeleteServerModal';
|
||||
import { EditServerFactory } from '../EditServer';
|
||||
import { ImportServersBtnFactory } from '../helpers/ImportServersBtn';
|
||||
import { ServerErrorFactory } from '../helpers/ServerError';
|
||||
import { withoutSelectedServer } from '../helpers/withoutSelectedServer';
|
||||
import { ManageServersFactory } from '../ManageServers';
|
||||
import { ManageServersRowFactory } from '../ManageServersRow';
|
||||
import { ManageServersRowDropdownFactory } from '../ManageServersRowDropdown';
|
||||
import { fetchServers } from '../reducers/remoteServers';
|
||||
import { selectServer } from '../reducers/selectedServer';
|
||||
import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
|
||||
import { ServersDropdown } from '../ServersDropdown';
|
||||
import { ServersExporter } from './ServersExporter';
|
||||
import { ServersImporter } from './ServersImporter';
|
||||
|
||||
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
export const provideServices = (bottle: Bottle) => {
|
||||
// Components
|
||||
bottle.factory('ManageServers', ManageServersFactory);
|
||||
bottle.decorator('ManageServers', withoutSelectedServer);
|
||||
bottle.decorator('ManageServers', connect(['servers'], []));
|
||||
|
||||
bottle.factory('ManageServersRow', ManageServersRowFactory);
|
||||
|
||||
bottle.factory('ManageServersRowDropdown', ManageServersRowDropdownFactory);
|
||||
bottle.decorator('ManageServersRowDropdown', connect(null, ['setAutoConnect']));
|
||||
|
||||
bottle.factory('CreateServer', CreateServerFactory);
|
||||
bottle.decorator('CreateServer', withoutSelectedServer);
|
||||
bottle.decorator('CreateServer', connect(['servers'], ['createServers']));
|
||||
|
||||
bottle.factory('EditServer', EditServerFactory);
|
||||
bottle.decorator('EditServer', connect([], ['editServer', 'selectServer']));
|
||||
|
||||
bottle.serviceFactory('ServersDropdown', () => ServersDropdown);
|
||||
bottle.decorator('ServersDropdown', connect(['servers']));
|
||||
|
||||
bottle.serviceFactory('DeleteServerModal', () => DeleteServerModal);
|
||||
bottle.decorator('DeleteServerModal', connect(null, ['deleteServer']));
|
||||
|
||||
bottle.factory('DeleteServerButton', DeleteServerButtonFactory);
|
||||
|
||||
bottle.factory('ImportServersBtn', ImportServersBtnFactory);
|
||||
bottle.decorator('ImportServersBtn', connect(['servers'], ['createServers']));
|
||||
|
||||
bottle.factory('ServerError', ServerErrorFactory);
|
||||
bottle.decorator('ServerError', connect(['servers']));
|
||||
|
||||
// Services
|
||||
bottle.service('ServersImporter', ServersImporter, 'csvToJson');
|
||||
bottle.service('ServersExporter', ServersExporter, 'Storage', 'window', 'jsonToCsv');
|
||||
|
||||
// Actions
|
||||
bottle.serviceFactory('selectServer', () => selectServer, 'buildShlinkApiClient', 'loadMercureInfo');
|
||||
bottle.serviceFactory('createServers', () => createServers);
|
||||
bottle.serviceFactory('deleteServer', () => deleteServer);
|
||||
bottle.serviceFactory('editServer', () => editServer);
|
||||
bottle.serviceFactory('setAutoConnect', () => setAutoConnect);
|
||||
bottle.serviceFactory('fetchServers', fetchServers, 'HttpClient');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user