mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
Feat/new addons table (#1021)
* feat: initial list of available addons * feat: add columns * fix: update referential equality * fix: remove search * fix: remove unused imports * fix: padding * fix: imports * refactor: based on comments
This commit is contained in:
parent
a791ea59c1
commit
ded3c22bb1
@ -0,0 +1,71 @@
|
||||
import { Avatar } from '@mui/material';
|
||||
import { DeviceHub } from '@mui/icons-material';
|
||||
import { formatAssetPath } from 'utils/formatPath';
|
||||
|
||||
import slackIcon from 'assets/icons/slack.svg';
|
||||
import jiraIcon from 'assets/icons/jira.svg';
|
||||
import webhooksIcon from 'assets/icons/webhooks.svg';
|
||||
import teamsIcon from 'assets/icons/teams.svg';
|
||||
import dataDogIcon from 'assets/icons/datadog.svg';
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
width: '32.5px',
|
||||
height: '32.5px',
|
||||
marginRight: '16px',
|
||||
borderRadius: '50%',
|
||||
};
|
||||
|
||||
interface IAddonIconProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const AddonIcon = ({ name }: IAddonIconProps) => {
|
||||
switch (name) {
|
||||
case 'slack':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="Slack logo"
|
||||
src={formatAssetPath(slackIcon)}
|
||||
/>
|
||||
);
|
||||
case 'jira-comment':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="JIRA logo"
|
||||
src={formatAssetPath(jiraIcon)}
|
||||
/>
|
||||
);
|
||||
case 'webhook':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="Generic Webhook logo"
|
||||
src={formatAssetPath(webhooksIcon)}
|
||||
/>
|
||||
);
|
||||
case 'teams':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="Microsoft Teams logo"
|
||||
src={formatAssetPath(teamsIcon)}
|
||||
/>
|
||||
);
|
||||
case 'datadog':
|
||||
return (
|
||||
<img
|
||||
style={style}
|
||||
alt="Datadog logo"
|
||||
src={formatAssetPath(dataDogIcon)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Avatar>
|
||||
<DeviceHub />
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
};
|
@ -13,13 +13,13 @@ import { formatAssetPath } from 'utils/formatPath';
|
||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
width: '32.5px',
|
||||
height: '32.5px',
|
||||
marginRight: '16px',
|
||||
float: 'left',
|
||||
borderRadius: '50%',
|
||||
};
|
||||
|
||||
const getAddonIcon = (name: string): ReactElement => {
|
||||
export const getAddonIcon = (name: string): ReactElement => {
|
||||
switch (name) {
|
||||
case 'slack':
|
||||
return (
|
||||
@ -71,20 +71,17 @@ const getAddonIcon = (name: string): ReactElement => {
|
||||
};
|
||||
|
||||
export const AddonList = () => {
|
||||
const { providers, addons } = useAddons();
|
||||
const { providers, addons, loading } = useAddons();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={addons.length > 0}
|
||||
show={<ConfiguredAddons getAddonIcon={getAddonIcon} />}
|
||||
show={<ConfiguredAddons />}
|
||||
/>
|
||||
|
||||
<br />
|
||||
<AvailableAddons
|
||||
providers={providers}
|
||||
getAddonIcon={getAddonIcon}
|
||||
/>
|
||||
<AvailableAddons loading={loading} providers={providers} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +1,24 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
} from '@mui/material';
|
||||
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
Table,
|
||||
SortableTableHeader,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TablePlaceholder,
|
||||
} from 'component/common/Table';
|
||||
|
||||
import { useTable, useSortBy } from 'react-table';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import { ConfigureAddonButton } from './ConfigureAddonButton/ConfigureAddonButton';
|
||||
import { AddonIcon } from '../AddonIcon/AddonIcon';
|
||||
|
||||
interface IProvider {
|
||||
name: string;
|
||||
@ -21,40 +30,153 @@ interface IProvider {
|
||||
}
|
||||
|
||||
interface IAvailableAddonsProps {
|
||||
getAddonIcon: (name: string) => ReactElement;
|
||||
providers: IProvider[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const AvailableAddons = ({
|
||||
providers,
|
||||
getAddonIcon,
|
||||
loading,
|
||||
}: IAvailableAddonsProps) => {
|
||||
const navigate = useNavigate();
|
||||
const data = useMemo(() => {
|
||||
if (loading) {
|
||||
return Array(5).fill({
|
||||
name: 'Provider name',
|
||||
description: 'Provider description when loading',
|
||||
});
|
||||
}
|
||||
|
||||
const renderProvider = (provider: IProvider) => (
|
||||
<ListItem key={provider.name}>
|
||||
<ListItemAvatar>{getAddonIcon(provider.name)}</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={provider.displayName}
|
||||
secondary={provider.description}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<PermissionButton
|
||||
permission={CREATE_ADDON}
|
||||
onClick={() => navigate(`/addons/create/${provider.name}`)}
|
||||
>
|
||||
Configure
|
||||
</PermissionButton>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
return providers.map(({ name, displayName, description }) => ({
|
||||
name,
|
||||
displayName,
|
||||
description,
|
||||
}));
|
||||
}, [providers, loading]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'Icon',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { name },
|
||||
},
|
||||
}: any) => {
|
||||
return (
|
||||
<PageContent header="Available addons">
|
||||
<List>
|
||||
{providers.map((provider: IProvider) =>
|
||||
renderProvider(provider)
|
||||
)}
|
||||
</List>
|
||||
<IconCell icon={<AddonIcon name={name as string} />} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
width: '90%',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { name, description },
|
||||
},
|
||||
}: any) => {
|
||||
return (
|
||||
<LinkCell
|
||||
data-loading
|
||||
title={name}
|
||||
subtitle={description}
|
||||
/>
|
||||
);
|
||||
},
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
<ActionCell>
|
||||
<ConfigureAddonButton name={original.name} />
|
||||
</ActionCell>
|
||||
),
|
||||
width: 150,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const initialState = useMemo(
|
||||
() => ({
|
||||
sortBy: [{ id: 'name', desc: false }],
|
||||
hiddenColumns: ['description'],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
state: { globalFilter },
|
||||
} = useTable(
|
||||
{
|
||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||
data,
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetGlobalFilter: false,
|
||||
autoResetSortBy: false,
|
||||
disableSortRemove: true,
|
||||
},
|
||||
useSortBy
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
header={<PageHeader title="Available addons" />}
|
||||
>
|
||||
<Table {...getTableProps()}>
|
||||
<SortableTableHeader headerGroups={headerGroups} />
|
||||
<TableBody {...getTableBodyProps()}>
|
||||
{rows.map(row => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<TableRow hover {...row.getRowProps()}>
|
||||
{row.cells.map(cell => (
|
||||
<TableCell {...cell.getCellProps()}>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={globalFilter?.length > 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No providers found matching “
|
||||
{globalFilter}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No providers available.
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,21 @@
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface IConfigureAddonButtonProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const ConfigureAddonButton = ({ name }: IConfigureAddonButtonProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<PermissionButton
|
||||
permission={CREATE_ADDON}
|
||||
variant="outlined"
|
||||
onClick={() => navigate(`/addons/create/${name}`)}
|
||||
>
|
||||
Configure
|
||||
</PermissionButton>
|
||||
);
|
||||
};
|
@ -1,38 +1,34 @@
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
} from '@mui/material';
|
||||
import { useMemo } from 'react';
|
||||
import { Box, Table, TableBody, TableCell, TableRow } from '@mui/material';
|
||||
import { Delete, Edit, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import {
|
||||
DELETE_ADDON,
|
||||
UPDATE_ADDON,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import useAddons from 'hooks/api/getters/useAddons/useAddons';
|
||||
import useToast from 'hooks/useToast';
|
||||
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||
import { ReactElement, useContext, useState } from 'react';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { IAddon } from 'interfaces/addons';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { useTable, useSortBy } from 'react-table';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { SortableTableHeader, TablePlaceholder } from 'component/common/Table';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
import { AddonIcon } from '../AddonIcon/AddonIcon';
|
||||
import { ConfiguredAddonsActionsCell } from './ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell';
|
||||
|
||||
interface IConfigureAddonsProps {
|
||||
getAddonIcon: (name: string) => ReactElement;
|
||||
}
|
||||
|
||||
export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
const { refetchAddons, addons } = useAddons();
|
||||
export const ConfiguredAddons = () => {
|
||||
const { refetchAddons, addons, loading } = useAddons();
|
||||
const { updateAddon, removeAddon } = useAddonsApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const navigate = useNavigate();
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const [deletedAddon, setDeletedAddon] = useState<IAddon>({
|
||||
id: 0,
|
||||
@ -43,15 +39,21 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
parameters: {},
|
||||
});
|
||||
|
||||
const sortAddons = (addons: IAddon[]) => {
|
||||
if (!addons) return [];
|
||||
|
||||
return addons.sort((addonA: IAddon, addonB: IAddon) => {
|
||||
return addonA.id - addonB.id;
|
||||
const data = useMemo(() => {
|
||||
if (loading) {
|
||||
return Array(5).fill({
|
||||
name: 'Addon name',
|
||||
description: 'Addon description when loading',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const toggleAddon = async (addon: IAddon) => {
|
||||
return addons.map(addon => ({
|
||||
...addon,
|
||||
}));
|
||||
}, [addons, loading]);
|
||||
|
||||
const toggleAddon = useCallback(
|
||||
async (addon: IAddon) => {
|
||||
try {
|
||||
await updateAddon({ ...addon, enabled: !addon.enabled });
|
||||
refetchAddons();
|
||||
@ -63,7 +65,91 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
},
|
||||
[setToastApiError, refetchAddons, setToastData, updateAddon]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'Icon',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { provider },
|
||||
},
|
||||
}: any) => (
|
||||
<IconCell icon={<AddonIcon name={provider as string} />} />
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'provider',
|
||||
width: '90%',
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { provider, description },
|
||||
},
|
||||
}: any) => {
|
||||
return (
|
||||
<LinkCell
|
||||
data-loading
|
||||
title={provider}
|
||||
subtitle={description}
|
||||
/>
|
||||
);
|
||||
},
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
<ConfiguredAddonsActionsCell
|
||||
setShowDelete={setShowDelete}
|
||||
toggleAddon={toggleAddon}
|
||||
setDeletedAddon={setDeletedAddon}
|
||||
original={original as IAddon}
|
||||
/>
|
||||
),
|
||||
width: 150,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[toggleAddon]
|
||||
);
|
||||
|
||||
const initialState = useMemo(
|
||||
() => ({
|
||||
sortBy: [{ id: 'provider', desc: false }],
|
||||
hiddenColumns: ['description'],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
state: { globalFilter },
|
||||
} = useTable(
|
||||
{
|
||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||
data,
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetGlobalFilter: false,
|
||||
autoResetSortBy: false,
|
||||
disableSortRemove: true,
|
||||
},
|
||||
useSortBy
|
||||
);
|
||||
|
||||
const onRemoveAddon = async (addon: IAddon) => {
|
||||
try {
|
||||
@ -74,78 +160,56 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||
title: 'Success',
|
||||
text: 'Deleted addon successfully',
|
||||
});
|
||||
} catch (e) {
|
||||
setToastData({
|
||||
type: 'error',
|
||||
title: 'Error',
|
||||
text: 'Can not delete addon',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const renderAddon = (addon: IAddon) => (
|
||||
<ListItem key={addon.id}>
|
||||
<ListItemAvatar>{getAddonIcon(addon.provider)}</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<span>
|
||||
<ConditionallyRender
|
||||
condition={hasAccess(UPDATE_ADDON)}
|
||||
show={
|
||||
<Link
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
}}
|
||||
to={`/addons/edit/${addon.id}`}
|
||||
>
|
||||
<strong>{addon.provider}</strong>
|
||||
</Link>
|
||||
}
|
||||
elseShow={<strong>{addon.provider}</strong>}
|
||||
/>
|
||||
{addon.enabled ? null : <small> (Disabled)</small>}
|
||||
</span>
|
||||
}
|
||||
secondary={addon.description}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
onClick={() => toggleAddon(addon)}
|
||||
tooltipProps={{ title: 'Toggle addon' }}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={addon.enabled}
|
||||
show={<Visibility />}
|
||||
elseShow={<VisibilityOff />}
|
||||
/>
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
tooltipProps={{ title: 'Edit Addon' }}
|
||||
onClick={() => navigate(`/addons/edit/${addon.id}`)}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_ADDON}
|
||||
tooltipProps={{ title: 'Remove Addon' }}
|
||||
onClick={() => {
|
||||
setDeletedAddon(addon);
|
||||
setShowDelete(true);
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
return (
|
||||
<PageContent header="Configured addons">
|
||||
<List>
|
||||
{sortAddons(addons).map((addon: IAddon) => renderAddon(addon))}
|
||||
</List>
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
header={<PageHeader title="Configured addons" />}
|
||||
>
|
||||
<Table {...getTableProps()}>
|
||||
<SortableTableHeader headerGroups={headerGroups} />
|
||||
<TableBody {...getTableBodyProps()}>
|
||||
{rows.map(row => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<TableRow hover {...row.getRowProps()}>
|
||||
{row.cells.map(cell => (
|
||||
<TableCell
|
||||
{...cell.getCellProps()}
|
||||
padding="none"
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={globalFilter?.length > 0}
|
||||
show={
|
||||
<TablePlaceholder>
|
||||
No addons found matching “
|
||||
{globalFilter}
|
||||
”
|
||||
</TablePlaceholder>
|
||||
}
|
||||
elseShow={
|
||||
<TablePlaceholder>
|
||||
No addons configured
|
||||
</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Dialogue
|
||||
open={showDelete}
|
||||
onClick={() => {
|
||||
|
@ -0,0 +1,58 @@
|
||||
import { Visibility, VisibilityOff, Edit, Delete } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import {
|
||||
UPDATE_ADDON,
|
||||
DELETE_ADDON,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { IAddon } from 'interfaces/addons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface IConfiguredAddonsActionsCellProps {
|
||||
toggleAddon: (addon: IAddon) => Promise<void>;
|
||||
original: IAddon;
|
||||
setShowDelete: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setDeletedAddon: React.Dispatch<React.SetStateAction<IAddon>>;
|
||||
}
|
||||
|
||||
export const ConfiguredAddonsActionsCell = ({
|
||||
toggleAddon,
|
||||
setShowDelete,
|
||||
setDeletedAddon,
|
||||
original,
|
||||
}: IConfiguredAddonsActionsCellProps) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ActionCell>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
onClick={() => toggleAddon(original)}
|
||||
tooltipProps={{ title: 'Toggle addon' }}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={original.enabled}
|
||||
show={<Visibility />}
|
||||
elseShow={<VisibilityOff />}
|
||||
/>
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
tooltipProps={{ title: 'Edit Addon' }}
|
||||
onClick={() => navigate(`/addons/edit/${original.id}`)}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
<PermissionIconButton
|
||||
permission={DELETE_ADDON}
|
||||
tooltipProps={{ title: 'Remove Addon' }}
|
||||
onClick={() => {
|
||||
setDeletedAddon(original);
|
||||
setShowDelete(true);
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
</ActionCell>
|
||||
);
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { IAddon } from 'interfaces/addons';
|
||||
import { useCallback } from 'react';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const useAddonsApi = () => {
|
||||
@ -15,13 +16,7 @@ const useAddonsApi = () => {
|
||||
body: JSON.stringify(addonConfig),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
return makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const removeAddon = async (id: number) => {
|
||||
@ -29,29 +24,22 @@ const useAddonsApi = () => {
|
||||
const req = createRequest(path, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
return await makeRequest(req.caller, req.id);
|
||||
};
|
||||
|
||||
const updateAddon = async (addonConfig: IAddon) => {
|
||||
const updateAddon = useCallback(
|
||||
async (addonConfig: IAddon) => {
|
||||
const path = `${URI}/${addonConfig.id}`;
|
||||
const req = createRequest(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(addonConfig),
|
||||
});
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
return makeRequest(req.caller, req.id);
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return {
|
||||
createAddon,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IAddon, IAddonProvider } from 'interfaces/addons';
|
||||
@ -23,9 +23,9 @@ const useAddons = (options: SWRConfiguration = {}) => {
|
||||
const { data, error } = useSWR<IAddonsResponse>(KEY, fetcher, options);
|
||||
const [loading, setLoading] = useState(!error && !data);
|
||||
|
||||
const refetchAddons = () => {
|
||||
const refetchAddons = useCallback(() => {
|
||||
mutate(KEY);
|
||||
};
|
||||
}, [KEY]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(!error && !data);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import UIContext from '../contexts/UIContext';
|
||||
import { IToast } from '../interfaces/toast';
|
||||
|
||||
@ -11,7 +11,8 @@ const useToast = () => {
|
||||
show: false,
|
||||
}));
|
||||
|
||||
const setToastApiError = (errorText: string, overrides?: IToast) => {
|
||||
const setToastApiError = useCallback(
|
||||
(errorText: string, overrides?: IToast) => {
|
||||
setToast({
|
||||
title: 'Something went wrong',
|
||||
text: `We had trouble talking to our API. Here's why: ${errorText}`,
|
||||
@ -20,15 +21,20 @@ const useToast = () => {
|
||||
autoHideDuration: 12000,
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
},
|
||||
[setToast]
|
||||
);
|
||||
|
||||
const setToastData = (toast: IToast) => {
|
||||
const setToastData = useCallback(
|
||||
(toast: IToast) => {
|
||||
if (toast.persist) {
|
||||
setToast({ ...toast, show: true });
|
||||
} else {
|
||||
setToast({ ...toast, show: true, autoHideDuration: 6000 });
|
||||
}
|
||||
};
|
||||
},
|
||||
[setToast]
|
||||
);
|
||||
|
||||
return { setToastData, setToastApiError, hideToast };
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user