mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-10-17 03:04:08 +00:00
Migrate from ESLint to Oxlint and Oxfmt
This commit is contained in:
@@ -38,25 +38,31 @@ const CreateServerBase: FC<CreateServerProps> = withoutSelectedServer(({ useTime
|
||||
const [errorImporting, setErrorImporting] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME });
|
||||
const { flag: isConfirmModalOpen, toggle: toggleConfirmModal } = useToggle();
|
||||
const [serverData, setServerData] = useState<ServerData>();
|
||||
const saveNewServer = useCallback((newServerData: ServerData) => {
|
||||
const [newServerWithUniqueId] = ensureUniqueIds(servers, [newServerData]);
|
||||
const saveNewServer = useCallback(
|
||||
(newServerData: ServerData) => {
|
||||
const [newServerWithUniqueId] = ensureUniqueIds(servers, [newServerData]);
|
||||
|
||||
createServers([newServerWithUniqueId]);
|
||||
navigate(`/server/${newServerWithUniqueId.id}`);
|
||||
}, [createServers, navigate, servers]);
|
||||
const onSubmit = useCallback((newServerData: ServerData) => {
|
||||
setServerData(newServerData);
|
||||
createServers([newServerWithUniqueId]);
|
||||
navigate(`/server/${newServerWithUniqueId.id}`);
|
||||
},
|
||||
[createServers, navigate, servers],
|
||||
);
|
||||
const onSubmit = useCallback(
|
||||
(newServerData: ServerData) => {
|
||||
setServerData(newServerData);
|
||||
|
||||
const serverExists = Object.values(servers).some(
|
||||
({ url, apiKey }) => newServerData.url === url && newServerData.apiKey === apiKey,
|
||||
);
|
||||
const serverExists = Object.values(servers).some(
|
||||
({ url, apiKey }) => newServerData.url === url && newServerData.apiKey === apiKey,
|
||||
);
|
||||
|
||||
if (serverExists) {
|
||||
toggleConfirmModal();
|
||||
} else {
|
||||
saveNewServer(newServerData);
|
||||
}
|
||||
}, [saveNewServer, servers, toggleConfirmModal]);
|
||||
if (serverExists) {
|
||||
toggleConfirmModal();
|
||||
} else {
|
||||
saveNewServer(newServerData);
|
||||
}
|
||||
},
|
||||
[saveNewServer, servers, toggleConfirmModal],
|
||||
);
|
||||
|
||||
return (
|
||||
<NoMenuLayout>
|
||||
@@ -64,7 +70,11 @@ const CreateServerBase: FC<CreateServerProps> = withoutSelectedServer(({ useTime
|
||||
{!hasServers && (
|
||||
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onError={setErrorImporting} />
|
||||
)}
|
||||
{hasServers && <Button variant="secondary" onClick={goBack}>Cancel</Button>}
|
||||
{hasServers && (
|
||||
<Button variant="secondary" onClick={goBack}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button type="submit">Create server</Button>
|
||||
</ServerForm>
|
||||
|
||||
|
||||
@@ -12,12 +12,15 @@ export type DeleteServerButtonProps = PropsWithChildren<{
|
||||
export const DeleteServerButton: FC<DeleteServerButtonProps> = ({ server, children }) => {
|
||||
const { flag: isModalOpen, setToTrue: showModal, setToFalse: hideModal } = useToggle();
|
||||
const navigate = useNavigate();
|
||||
const onClose = useCallback((confirmed: boolean) => {
|
||||
hideModal();
|
||||
if (confirmed) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [hideModal, navigate]);
|
||||
const onClose = useCallback(
|
||||
(confirmed: boolean) => {
|
||||
hideModal();
|
||||
if (confirmed) {
|
||||
navigate('/');
|
||||
}
|
||||
},
|
||||
[hideModal, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -13,11 +13,14 @@ export type DeleteServerModalProps = {
|
||||
|
||||
export const DeleteServerModal: FC<DeleteServerModalProps> = ({ server, onClose, open }) => {
|
||||
const { deleteServer } = useServers();
|
||||
const onClosed = useCallback((exitAction: ExitAction) => {
|
||||
if (exitAction === 'confirm') {
|
||||
deleteServer(server);
|
||||
}
|
||||
}, [deleteServer, server]);
|
||||
const onClosed = useCallback(
|
||||
(exitAction: ExitAction) => {
|
||||
if (exitAction === 'confirm') {
|
||||
deleteServer(server);
|
||||
}
|
||||
},
|
||||
[deleteServer, server],
|
||||
);
|
||||
|
||||
return (
|
||||
<CardModal
|
||||
@@ -30,11 +33,13 @@ export const DeleteServerModal: FC<DeleteServerModalProps> = ({ server, onClose,
|
||||
confirmText="Delete"
|
||||
>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<p>Are you sure you want to remove <b>{server ? server.name : ''}</b>?</p>
|
||||
<p>
|
||||
Are you sure you want to remove <b>{server ? server.name : ''}</b>?
|
||||
</p>
|
||||
<p>
|
||||
<i>
|
||||
No data will be deleted, only the access to this server will be removed from this device.
|
||||
You can create it again at any moment.
|
||||
No data will be deleted, only the access to this server will be removed from this device. You can create it
|
||||
again at any moment.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,9 @@ export const EditServer: FC = withSelectedServer(() => {
|
||||
initialValues={selectedServer}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Button variant="secondary" onClick={goBack}>Cancel</Button>
|
||||
<Button variant="secondary" onClick={goBack}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Save</Button>
|
||||
</ServerForm>
|
||||
</NoMenuLayout>
|
||||
|
||||
@@ -19,66 +19,75 @@ export type ManageServersProps = {
|
||||
|
||||
const SHOW_IMPORT_MSG_TIME = 4000;
|
||||
|
||||
const ManageServersBase: FC<ManageServersProps> = withoutSelectedServer(({
|
||||
ServersExporter: serversExporter,
|
||||
useTimeoutToggle,
|
||||
}) => {
|
||||
const { servers } = useServers();
|
||||
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 = allServers.some(({ autoConnect }) => !!autoConnect);
|
||||
const ManageServersBase: FC<ManageServersProps> = withoutSelectedServer(
|
||||
({ ServersExporter: serversExporter, useTimeoutToggle }) => {
|
||||
const { servers } = useServers();
|
||||
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 = allServers.some(({ autoConnect }) => !!autoConnect);
|
||||
|
||||
const [errorImporting, setErrorImporting] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME });
|
||||
const [errorImporting, setErrorImporting] = useTimeoutToggle({ delay: SHOW_IMPORT_MSG_TIME });
|
||||
|
||||
return (
|
||||
<NoMenuLayout className="flex flex-col gap-y-4">
|
||||
<SearchInput onChange={setSearchTerm} />
|
||||
return (
|
||||
<NoMenuLayout className="flex flex-col gap-y-4">
|
||||
<SearchInput onChange={setSearchTerm} />
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-2">
|
||||
<div className="flex gap-2">
|
||||
<ImportServersBtn className="flex-grow" onError={setErrorImporting}>Import servers</ImportServersBtn>
|
||||
{filteredServers.length > 0 && (
|
||||
<Button variant="secondary" className="flex-grow" onClick={async () => serversExporter.exportServers()}>
|
||||
<FontAwesomeIcon icon={exportIcon} widthAuto /> Export servers
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button className="md:ml-auto" to="/server/create">
|
||||
<FontAwesomeIcon icon={plusIcon} widthAuto /> Add a server
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SimpleCard className="card">
|
||||
<Table header={(
|
||||
<Table.Row>
|
||||
{hasAutoConnect && (
|
||||
<Table.Cell className="w-[35px]"><span className="sr-only">Auto-connect</span></Table.Cell>
|
||||
<div className="flex flex-col md:flex-row gap-2">
|
||||
<div className="flex gap-2">
|
||||
<ImportServersBtn className="flex-grow" onError={setErrorImporting}>
|
||||
Import servers
|
||||
</ImportServersBtn>
|
||||
{filteredServers.length > 0 && (
|
||||
<Button variant="secondary" className="flex-grow" onClick={async () => serversExporter.exportServers()}>
|
||||
<FontAwesomeIcon icon={exportIcon} widthAuto /> Export servers
|
||||
</Button>
|
||||
)}
|
||||
<Table.Cell>Name</Table.Cell>
|
||||
<Table.Cell>Base URL</Table.Cell>
|
||||
<Table.Cell><span className="sr-only">Options</span></Table.Cell>
|
||||
</Table.Row>
|
||||
)}>
|
||||
{!filteredServers.length && (
|
||||
<Table.Row className="text-center"><Table.Cell colSpan={4}>No servers found.</Table.Cell></Table.Row>
|
||||
)}
|
||||
{filteredServers.map((server) => (
|
||||
<ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} />
|
||||
))}
|
||||
</Table>
|
||||
</SimpleCard>
|
||||
|
||||
{errorImporting && (
|
||||
<div>
|
||||
<Result variant="error">The servers could not be imported. Make sure the format is correct.</Result>
|
||||
</div>
|
||||
<Button className="md:ml-auto" to="/server/create">
|
||||
<FontAwesomeIcon icon={plusIcon} widthAuto /> Add a server
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</NoMenuLayout>
|
||||
);
|
||||
});
|
||||
|
||||
<SimpleCard className="card">
|
||||
<Table
|
||||
header={
|
||||
<Table.Row>
|
||||
{hasAutoConnect && (
|
||||
<Table.Cell className="w-[35px]">
|
||||
<span className="sr-only">Auto-connect</span>
|
||||
</Table.Cell>
|
||||
)}
|
||||
<Table.Cell>Name</Table.Cell>
|
||||
<Table.Cell>Base URL</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span className="sr-only">Options</span>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
}
|
||||
>
|
||||
{!filteredServers.length && (
|
||||
<Table.Row className="text-center">
|
||||
<Table.Cell colSpan={4}>No servers found.</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
{filteredServers.map((server) => (
|
||||
<ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} />
|
||||
))}
|
||||
</Table>
|
||||
</SimpleCard>
|
||||
|
||||
{errorImporting && (
|
||||
<div>
|
||||
<Result variant="error">The servers could not be imported. Make sure the format is correct.</Result>
|
||||
</div>
|
||||
)}
|
||||
</NoMenuLayout>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const ManageServers = withDependencies(ManageServersBase, ['ServersExporter', 'useTimeoutToggle']);
|
||||
|
||||
@@ -34,7 +34,9 @@ export const ManageServersRow: FC<ManageServersRowProps> = ({ server, hasAutoCon
|
||||
<Table.Cell className="font-bold" columnName="Name">
|
||||
<Link to={`/server/${server.id}`}>{server.name}</Link>
|
||||
</Table.Cell>
|
||||
<Table.Cell columnName="Base URL" className="max-lg:border-b-0">{server.url}</Table.Cell>
|
||||
<Table.Cell columnName="Base URL" className="max-lg:border-b-0">
|
||||
{server.url}
|
||||
</Table.Cell>
|
||||
<Table.Cell className="text-right max-lg:absolute right-0 -top-1 mx-lg:pt-0">
|
||||
<ManageServersRowDropdown server={server} />
|
||||
</Table.Cell>
|
||||
|
||||
@@ -12,11 +12,13 @@ export const ServersDropdown: FC = () => {
|
||||
const { selectedServer } = useSelectedServer();
|
||||
|
||||
return (
|
||||
<NavBar.Dropdown buttonContent={(
|
||||
<span className="flex items-center gap-1.5">
|
||||
<FontAwesomeIcon icon={serverIcon} /> Servers
|
||||
</span>
|
||||
)}>
|
||||
<NavBar.Dropdown
|
||||
buttonContent={
|
||||
<span className="flex items-center gap-1.5">
|
||||
<FontAwesomeIcon icon={serverIcon} /> Servers
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{serversList.length === 0 ? (
|
||||
<Dropdown.Item to="/server/create">
|
||||
<FontAwesomeIcon icon={plusIcon} /> Add a server
|
||||
|
||||
@@ -25,7 +25,7 @@ const ServerListItem = ({ id, name }: { id: string; name: string }) => (
|
||||
</Link>
|
||||
);
|
||||
|
||||
export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, borderless }) => (
|
||||
export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, borderless }) =>
|
||||
servers.length > 0 && (
|
||||
<div
|
||||
data-testid="list"
|
||||
@@ -35,7 +35,8 @@ export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, borderles
|
||||
{ 'border-y': !borderless },
|
||||
)}
|
||||
>
|
||||
{servers.map(({ id, name }) => <ServerListItem key={id} id={id} name={name} />)}
|
||||
{servers.map(({ id, name }) => (
|
||||
<ServerListItem key={id} id={id} name={name} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
@@ -10,9 +10,12 @@ export type DuplicatedServersModalProps = {
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = (
|
||||
{ open, duplicatedServers, onClose, onConfirm },
|
||||
) => {
|
||||
export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = ({
|
||||
open,
|
||||
duplicatedServers,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const hasMultipleServers = duplicatedServers.length > 1;
|
||||
|
||||
return (
|
||||
@@ -27,16 +30,24 @@ export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = (
|
||||
>
|
||||
<p>{hasMultipleServers ? 'The next servers already exist:' : 'There is already a server with:'}</p>
|
||||
<ul className="list-disc my-4 pl-5">
|
||||
{duplicatedServers.map(({ url, apiKey }, index) => (!hasMultipleServers ? (
|
||||
<Fragment key={index}>
|
||||
<li>URL: <b>{url}</b></li>
|
||||
<li>API key: <b>{apiKey}</b></li>
|
||||
</Fragment>
|
||||
) : <li key={index}><b>{url}</b> - <b>{apiKey}</b></li>))}
|
||||
{duplicatedServers.map(({ url, apiKey }, index) =>
|
||||
!hasMultipleServers ? (
|
||||
<Fragment key={index}>
|
||||
<li>
|
||||
URL: <b>{url}</b>
|
||||
</li>
|
||||
<li>
|
||||
API key: <b>{apiKey}</b>
|
||||
</li>
|
||||
</Fragment>
|
||||
) : (
|
||||
<li key={index}>
|
||||
<b>{url}</b> - <b>{apiKey}</b>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
<span>
|
||||
{hasMultipleServers ? 'Do you want to save duplicated servers' : 'Do you want to save this server'}?
|
||||
</span>
|
||||
<span>{hasMultipleServers ? 'Do you want to save duplicated servers' : 'Do you want to save this server'}?</span>
|
||||
</CardModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ export type ImportServersBtnProps = PropsWithChildren<{
|
||||
className?: string;
|
||||
|
||||
// Injected
|
||||
ServersImporter: ServersImporter
|
||||
ServersImporter: ServersImporter;
|
||||
}>;
|
||||
|
||||
const ImportServersBtnBase: FC<ImportServersBtnProps> = ({
|
||||
@@ -37,7 +37,8 @@ const ImportServersBtnBase: FC<ImportServersBtnProps> = ({
|
||||
|
||||
const onFile = useCallback(
|
||||
async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
||||
serversImporter.importServersFromFile(target.files?.[0])
|
||||
serversImporter
|
||||
.importServersFromFile(target.files?.[0])
|
||||
.then((importedServers) => {
|
||||
const { duplicatedServers, newServers } = dedupServers(servers, importedServers);
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ export const ServerError: FC = () => {
|
||||
</Message>
|
||||
|
||||
<p className="text-xl">
|
||||
These are the Shlink servers currently configured. Choose one of
|
||||
them or <Link to="/server/create">add a new one</Link>.
|
||||
These are the Shlink servers currently configured. Choose one of them or{' '}
|
||||
<Link to="/server/create">add a new one</Link>.
|
||||
</p>
|
||||
<Card className="w-full max-w-100 overflow-hidden">
|
||||
<ServersListGroup borderless servers={Object.values(servers)} />
|
||||
@@ -35,9 +35,8 @@ export const ServerError: FC = () => {
|
||||
|
||||
{isServerWithId(selectedServer) && (
|
||||
<p className="text-xl">
|
||||
Alternatively, if you think you may have misconfigured this server, you
|
||||
can <DeleteServerButton server={selectedServer}>remove
|
||||
it</DeleteServerButton> or
|
||||
Alternatively, if you think you may have misconfigured this server, you can{' '}
|
||||
<DeleteServerButton server={selectedServer}>remove it</DeleteServerButton> or
|
||||
<Link to={`/server/${selectedServer.id}/edit?reconnect=true`}>edit it</Link>.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -45,13 +45,13 @@ export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, child
|
||||
Forward credentials to this server on every request.
|
||||
</Label>
|
||||
<small className="pl-5.5 text-gray-600 dark:text-gray-400 mt-0.5">
|
||||
{'"'}Credentials{'"'} here means cookies, TLS client certificates, or authentication headers containing a username
|
||||
and password.
|
||||
{'"'}Credentials{'"'} here means cookies, TLS client certificates, or authentication headers containing a
|
||||
username and password.
|
||||
</small>
|
||||
<small className="pl-5.5 text-gray-600 dark:text-gray-400">
|
||||
<b>Important!</b> If you are not sure what this means, leave it unchecked. Enabling this option will
|
||||
make all requests fail for Shlink older than v4.5.0, as it requires the server to set a more strict
|
||||
value for <code className="whitespace-nowrap">Access-Control-Allow-Origin</code> than <code>*</code>.
|
||||
<b>Important!</b> If you are not sure what this means, leave it unchecked. Enabling this option will make
|
||||
all requests fail for Shlink older than v4.5.0, as it requires the server to set a more strict value for{' '}
|
||||
<code className="whitespace-nowrap">Access-Control-Allow-Origin</code> than <code>*</code>.
|
||||
</small>
|
||||
</div>
|
||||
</Details>
|
||||
|
||||
@@ -49,9 +49,9 @@ export type DedupServersResult = {
|
||||
*/
|
||||
export function dedupServers(servers: ServersMap, serversToAdd: ServerData[]): DedupServersResult {
|
||||
const serversList = Object.values(servers);
|
||||
const { duplicatedServers = [], newServers = [] } = groupBy(
|
||||
serversToAdd,
|
||||
(server) => serversInclude(serversList, server) ? 'duplicatedServers' : 'newServers',
|
||||
// oxlint-disable-next-line typescript/no-useless-default-assignment - Type definition is wrong
|
||||
const { duplicatedServers = [], newServers = [] } = groupBy(serversToAdd, (server) =>
|
||||
serversInclude(serversList, server) ? 'duplicatedServers' : 'newServers',
|
||||
);
|
||||
|
||||
return { duplicatedServers, newServers };
|
||||
|
||||
@@ -8,10 +8,7 @@ import { hasServerData } from '../data';
|
||||
import { ensureUniqueIds } from '../helpers';
|
||||
import { createServers, useServers } from './servers';
|
||||
|
||||
const responseToServersList = (data: any) => ensureUniqueIds(
|
||||
{},
|
||||
(Array.isArray(data) ? data.filter(hasServerData) : []),
|
||||
);
|
||||
const responseToServersList = (data: any) => ensureUniqueIds({}, Array.isArray(data) ? data.filter(hasServerData) : []);
|
||||
|
||||
export const fetchServers = createAsyncThunk(
|
||||
'shlink/remoteServers/fetchServers',
|
||||
|
||||
@@ -15,17 +15,16 @@ export const MIN_FALLBACK_VERSION = '1.0.0';
|
||||
export const MAX_FALLBACK_VERSION = '999.999.999';
|
||||
export const LATEST_VERSION_CONSTRAINT = 'latest';
|
||||
|
||||
const versionToSemVer = (version: string) => toSemVer(
|
||||
version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version,
|
||||
MIN_FALLBACK_VERSION,
|
||||
);
|
||||
const versionToSemVer = (version: string) =>
|
||||
toSemVer(version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, MIN_FALLBACK_VERSION);
|
||||
|
||||
const getServerVersion = memoizeWith(
|
||||
(server: ServerWithId) => `${server.id}_${server.url}_${server.apiKey}`,
|
||||
async (_server: ServerWithId, health: () => Promise<ShlinkHealth>) => health().then(({ version }) => ({
|
||||
version: versionToSemVer(version),
|
||||
printableVersion: versionToPrintable(version),
|
||||
})),
|
||||
async (_server: ServerWithId, health: () => Promise<ShlinkHealth>) =>
|
||||
health().then(({ version }) => ({
|
||||
version: versionToSemVer(version),
|
||||
printableVersion: versionToPrintable(version),
|
||||
})),
|
||||
);
|
||||
|
||||
const initialState: SelectedServer = null;
|
||||
|
||||
@@ -27,9 +27,7 @@ export const { actions, reducer: serversReducer } = createSlice({
|
||||
}),
|
||||
reducer: (state, { payload }: PayloadAction<EditServer>) => {
|
||||
const { serverId, serverData } = payload;
|
||||
return (
|
||||
!state[serverId] ? state : { ...state, [serverId]: { ...state[serverId], ...serverData } }
|
||||
);
|
||||
return !state[serverId] ? state : { ...state, [serverId]: { ...state[serverId], ...serverData } };
|
||||
},
|
||||
},
|
||||
deleteServer: (state, { payload }) => {
|
||||
|
||||
Reference in New Issue
Block a user