diff --git a/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx b/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx
index c4a0304e4c..2fb1ccfd5e 100644
--- a/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx
+++ b/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx
@@ -12,7 +12,6 @@ const style: React.CSSProperties = {
width: '32.5px',
height: '32.5px',
marginRight: '16px',
- borderRadius: '50%',
};
interface IAddonIconProps {
diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
index 4c67c01fe0..ed8d5486a4 100644
--- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
+++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
@@ -87,7 +87,6 @@ export const AvailableAddons = ({
sortType: 'alphanumeric',
},
{
- Header: 'Actions',
id: 'Actions',
align: 'center',
Cell: ({ row: { original } }: any) => (
diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
index b80b2d2a08..b6490cfffb 100644
--- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
+++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
@@ -52,10 +52,13 @@ export const ConfiguredAddons = () => {
setToastData({
type: 'success',
title: 'Success',
- text: 'Addon state switched successfully',
+ text: !addon.enabled
+ ? 'Addon is now active'
+ : 'Addon is now disabled',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
+ throw error; // caught by optimistic update
}
},
[setToastApiError, refetchAddons, setToastData, updateAddon]
@@ -96,12 +99,16 @@ export const ConfiguredAddons = () => {
Header: 'Actions',
id: 'Actions',
align: 'center',
- Cell: ({ row: { original } }: any) => (
+ Cell: ({
+ row: { original },
+ }: {
+ row: { original: IAddon };
+ }) => (
),
width: 150,
diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx
index 0e12b607ec..f2a9e21136 100644
--- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx
+++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx
@@ -1,7 +1,9 @@
-import { Visibility, VisibilityOff, Edit, Delete } from '@mui/icons-material';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { Edit, Delete } from '@mui/icons-material';
+import { Tooltip } from '@mui/material';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
+import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
+import { useOptimisticUpdate } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/hooks/useOptimisticUpdate';
import {
UPDATE_ADDON,
DELETE_ADDON,
@@ -23,19 +25,32 @@ export const ConfiguredAddonsActionsCell = ({
original,
}: IConfiguredAddonsActionsCellProps) => {
const navigate = useNavigate();
+ const [isEnabled, setIsEnabled, rollbackIsChecked] =
+ useOptimisticUpdate(original.enabled);
+
+ const onClick = () => {
+ setIsEnabled(!isEnabled);
+ toggleAddon(original).catch(rollbackIsChecked);
+ };
+
return (
- toggleAddon(original)}
- tooltipProps={{ title: 'Toggle addon' }}
+
- }
- elseShow={}
+
-
+
+
({
+ container: {
+ display: 'flex',
+ justifyContent: 'flex-end',
+ alignItems: 'center',
+ padding: theme.spacing(0, 1.5),
+ },
+ divider: {
+ borderColor: theme.palette.dividerAlternative,
+ height: theme.spacing(3),
+ margin: theme.spacing(0, 2),
+ },
+}));
diff --git a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx
index ff89762d43..30637e2c72 100644
--- a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx
+++ b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx
@@ -1,14 +1,26 @@
-import { Box } from '@mui/material';
-import { ReactNode } from 'react';
+import { Box, Divider } from '@mui/material';
+import { FC, VFC } from 'react';
+import { useStyles } from './ActionCell.styles';
-interface IContextActionsCellProps {
- children: ReactNode;
-}
-
-export const ActionCell = ({ children }: IContextActionsCellProps) => {
+const ActionCellDivider: VFC = () => {
+ const { classes } = useStyles();
return (
-
- {children}
-
+
);
};
+
+const ActionCellComponent: FC & {
+ Divider: typeof ActionCellDivider;
+} = ({ children }) => {
+ const { classes } = useStyles();
+
+ return {children};
+};
+
+ActionCellComponent.Divider = ActionCellDivider;
+
+export const ActionCell = ActionCellComponent;
diff --git a/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts b/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts
index 8d687a8434..eeb57d6092 100644
--- a/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts
+++ b/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts
@@ -22,7 +22,7 @@ export const useStyles = makeStyles()(theme => ({
infoContainer: {
marginTop: '1rem',
display: 'flex',
- justifyContent: 'space-between',
+ justifyContent: 'space-around',
},
infoInnerContainer: {
textAlign: 'center',
diff --git a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx
similarity index 80%
rename from frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx
rename to frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx
index 9ba499b8ad..baf4ca0b9c 100644
--- a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx
+++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx
@@ -3,12 +3,7 @@ import {
DELETE_ENVIRONMENT,
UPDATE_ENVIRONMENT,
} from 'component/providers/AccessProvider/permissions';
-import {
- Edit,
- Delete,
- DragIndicator,
- PowerSettingsNew,
-} from '@mui/icons-material';
+import { Edit, Delete } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IconButton, Tooltip } from '@mui/material';
import { useNavigate } from 'react-router-dom';
@@ -16,14 +11,14 @@ import AccessContext from 'contexts/AccessContext';
import { useContext, useState } from 'react';
import { IEnvironment } from 'interfaces/environments';
import { formatUnknownError } from 'utils/formatUnknownError';
-import EnvironmentToggleConfirm from '../EnvironmentToggleConfirm/EnvironmentToggleConfirm';
-import EnvironmentDeleteConfirm from '../EnvironmentDeleteConfirm/EnvironmentDeleteConfirm';
+import EnvironmentToggleConfirm from '../../EnvironmentToggleConfirm/EnvironmentToggleConfirm';
+import EnvironmentDeleteConfirm from '../../EnvironmentDeleteConfirm/EnvironmentDeleteConfirm';
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import useToast from 'hooks/useToast';
import { useId } from 'hooks/useId';
-import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
interface IEnvironmentTableActionsProps {
environment: IEnvironment;
@@ -35,7 +30,6 @@ export const EnvironmentActionCell = ({
const navigate = useNavigate();
const { hasAccess } = useContext(AccessContext);
const updatePermission = hasAccess(UPDATE_ENVIRONMENT);
- const { searchQuery } = useSearchHighlightContext();
const { setToastApiError, setToastData } = useToast();
const { refetchEnvironments } = useEnvironments();
@@ -73,8 +67,8 @@ export const EnvironmentActionCell = ({
const handleToggleEnvironmentOn = async () => {
try {
- await toggleEnvironmentOn(environment.name);
setToggleModal(false);
+ await toggleEnvironmentOn(environment.name);
setToastData({
type: 'success',
title: 'Project environment enabled',
@@ -88,8 +82,8 @@ export const EnvironmentActionCell = ({
const handleToggleEnvironmentOff = async () => {
try {
- await toggleEnvironmentOff(environment.name);
setToggleModal(false);
+ await toggleEnvironmentOff(environment.name);
setToastData({
type: 'success',
title: 'Project environment disabled',
@@ -102,37 +96,28 @@ export const EnvironmentActionCell = ({
};
const toggleIconTooltip = environment.enabled
- ? 'Disable environment'
- : 'Enable environment';
+ ? `Disable environment ${environment.name}`
+ : `Enable environment ${environment.name}`;
const editId = useId();
const deleteId = useId();
- // Allow drag and drop if the user is permitted to reorder environments.
- // Disable drag and drop while searching since some rows may be hidden.
- const enableDragAndDrop = updatePermission && !searchQuery;
-
return (
-
-
-
- }
- />
- setToggleModal(true)}
- size="large"
- >
-
-
-
+ <>
+
+ setToggleModal(true)}
+ disabled={environment.protected}
+ />
+
+
+ >
}
/>
diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx
new file mode 100644
index 0000000000..66c3b2dd8a
--- /dev/null
+++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx
@@ -0,0 +1,42 @@
+import { useContext, VFC } from 'react';
+import { styled } from '@mui/material';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import { Box, IconButton } from '@mui/material';
+import { CloudCircle, DragIndicator } from '@mui/icons-material';
+import { UPDATE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions';
+import AccessContext from 'contexts/AccessContext';
+
+const DragIcon = styled(IconButton)(
+ ({ theme }) => `
+ padding: ${theme.spacing(0, 1, 0, 0)};
+ cursor: inherit;
+ transition: color 0.2s ease-in-out;
+ `
+);
+
+export const EnvironmentIconCell: VFC = () => {
+ const { hasAccess } = useContext(AccessContext);
+ const updatePermission = hasAccess(UPDATE_ENVIRONMENT);
+ const { searchQuery } = useSearchHighlightContext();
+
+ // Allow drag and drop if the user is permitted to reorder environments.
+ // Disable drag and drop while searching since some rows may be hidden.
+ const enableDragAndDrop = updatePermission && !searchQuery;
+ return (
+
+
+
+
+ }
+ />
+
+
+ );
+};
diff --git a/frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx
similarity index 85%
rename from frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx
rename to frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx
index b13f44e363..6312722f8d 100644
--- a/frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx
+++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx
@@ -21,6 +21,10 @@ export const EnvironmentNameCell = ({
condition={!environment.enabled}
show={Disabled}
/>
+ Predefined}
+ />
);
};
diff --git a/frontend/src/component/environments/EnvironmentRow/EnvironmentRow.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentRow/EnvironmentRow.tsx
similarity index 100%
rename from frontend/src/component/environments/EnvironmentRow/EnvironmentRow.tsx
rename to frontend/src/component/environments/EnvironmentTable/EnvironmentRow/EnvironmentRow.tsx
diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx
index e58eb81189..b9d8dde01c 100644
--- a/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx
+++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx
@@ -11,11 +11,6 @@ import {
import { useCallback } from 'react';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { Alert, styled, TableBody } from '@mui/material';
-import { CloudCircle } from '@mui/icons-material';
-import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
-import { EnvironmentActionCell } from 'component/environments/EnvironmentActionCell/EnvironmentActionCell';
-import { EnvironmentNameCell } from 'component/environments/EnvironmentNameCell/EnvironmentNameCell';
-import { EnvironmentRow } from 'component/environments/EnvironmentRow/EnvironmentRow';
import { MoveListItem } from 'hooks/useDragItem';
import useToast from 'hooks/useToast';
import useEnvironmentApi, {
@@ -23,6 +18,10 @@ import useEnvironmentApi, {
} from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { EnvironmentRow } from './EnvironmentRow/EnvironmentRow';
+import { EnvironmentNameCell } from './EnvironmentNameCell/EnvironmentNameCell';
+import { EnvironmentActionCell } from './EnvironmentActionCell/EnvironmentActionCell';
+import { EnvironmentIconCell } from './EnvironmentIconCell/EnvironmentIconCell';
import { Search } from 'component/common/Search/Search';
const StyledAlert = styled(Alert)(({ theme }) => ({
@@ -137,7 +136,7 @@ const COLUMNS = [
{
id: 'Icon',
width: '1%',
- Cell: () => } />,
+ Cell: () => ,
disableGlobalFilter: true,
},
{
diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx
index cad1b6eeeb..a7988a1c23 100644
--- a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx
+++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx
@@ -1,17 +1,7 @@
-import { useState, useMemo } from 'react';
+import { useState, useMemo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
-import { IconButton, Tooltip, Box } from '@mui/material';
-import {
- Delete,
- Edit,
- Extension,
- Visibility,
- VisibilityOff,
-} from '@mui/icons-material';
-import {
- DELETE_STRATEGY,
- UPDATE_STRATEGY,
-} from 'component/providers/AccessProvider/permissions';
+import { Box } from '@mui/material';
+import { Extension } from '@mui/icons-material';
import {
Table,
SortableTableHeader,
@@ -20,11 +10,11 @@ import {
TableRow,
TablePlaceholder,
} from 'component/common/Table';
+import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
-import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { formatStrategyName } from 'utils/strategyNames';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi';
@@ -37,6 +27,9 @@ import { sortTypes } from 'utils/sortTypes';
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton';
import { StatusBadge } from 'component/common/StatusBadge/StatusBadge';
+import { StrategySwitch } from './StrategySwitch/StrategySwitch';
+import { StrategyEditButton } from './StrategyEditButton/StrategyEditButton';
+import { StrategyDeleteButton } from './StrategyDeleteButton/StrategyDeleteButton';
import { Search } from 'component/common/Search/Search';
interface IDialogueMetaData {
@@ -78,6 +71,85 @@ export const StrategiesList = () => {
);
}, [strategies, loading]);
+ const onToggle = useCallback(
+ (strategy: IStrategy) => (deprecated: boolean) => {
+ if (deprecated) {
+ setDialogueMetaData({
+ show: true,
+ title: 'Really reactivate strategy?',
+ onConfirm: async () => {
+ try {
+ await reactivateStrategy(strategy);
+ refetchStrategies();
+ setToastData({
+ type: 'success',
+ title: 'Success',
+ text: 'Strategy reactivated successfully',
+ });
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ },
+ });
+ } else {
+ setDialogueMetaData({
+ show: true,
+ title: 'Really deprecate strategy?',
+ onConfirm: async () => {
+ try {
+ await deprecateStrategy(strategy);
+ refetchStrategies();
+ setToastData({
+ type: 'success',
+ title: 'Success',
+ text: 'Strategy deprecated successfully',
+ });
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ },
+ });
+ }
+ },
+ [
+ deprecateStrategy,
+ reactivateStrategy,
+ refetchStrategies,
+ setToastApiError,
+ setToastData,
+ ]
+ );
+
+ const onDeleteStrategy = useCallback(
+ (strategy: IStrategy) => {
+ setDialogueMetaData({
+ show: true,
+ title: 'Really delete strategy?',
+ onConfirm: async () => {
+ try {
+ await removeStrategy(strategy);
+ refetchStrategies();
+ setToastData({
+ type: 'success',
+ title: 'Success',
+ text: 'Strategy deleted successfully',
+ });
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ },
+ });
+ },
+ [removeStrategy, refetchStrategies, setToastApiError, setToastData]
+ );
+
+ const onEditStrategy = useCallback(
+ (strategy: IStrategy) => {
+ navigate(`/strategies/${strategy.name}/edit`);
+ },
+ [navigate]
+ );
+
const columns = useMemo(
() => [
{
@@ -86,7 +158,7 @@ export const StrategiesList = () => {
{
id: 'Actions',
align: 'center',
Cell: ({ row: { original } }: any) => (
-
-
+
- {editButton(original)}
- {deleteButton(original)}
-
+
+ onEditStrategy(original)}
+ />
+ onDeleteStrategy(original)}
+ />
+
),
width: 150,
disableGlobalFilter: true,
@@ -161,8 +237,7 @@ export const StrategiesList = () => {
sortType: 'number',
},
],
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
+ [onToggle, onEditStrategy, onDeleteStrategy]
);
const initialState = useMemo(
@@ -195,152 +270,6 @@ export const StrategiesList = () => {
useSortBy
);
- const onReactivateStrategy = (strategy: IStrategy) => {
- setDialogueMetaData({
- show: true,
- title: 'Really reactivate strategy?',
- onConfirm: async () => {
- try {
- await reactivateStrategy(strategy);
- refetchStrategies();
- setToastData({
- type: 'success',
- title: 'Success',
- text: 'Strategy reactivated successfully',
- });
- } catch (error: unknown) {
- setToastApiError(formatUnknownError(error));
- }
- },
- });
- };
-
- const onDeprecateStrategy = (strategy: IStrategy) => {
- setDialogueMetaData({
- show: true,
- title: 'Really deprecate strategy?',
- onConfirm: async () => {
- try {
- await deprecateStrategy(strategy);
- refetchStrategies();
- setToastData({
- type: 'success',
- title: 'Success',
- text: 'Strategy deprecated successfully',
- });
- } catch (error: unknown) {
- setToastApiError(formatUnknownError(error));
- }
- },
- });
- };
-
- const onDeleteStrategy = (strategy: IStrategy) => {
- setDialogueMetaData({
- show: true,
- title: 'Really delete strategy?',
- onConfirm: async () => {
- try {
- await removeStrategy(strategy);
- refetchStrategies();
- setToastData({
- type: 'success',
- title: 'Success',
- text: 'Strategy deleted successfully',
- });
- } catch (error: unknown) {
- setToastApiError(formatUnknownError(error));
- }
- },
- });
- };
-
- const reactivateButton = (strategy: IStrategy) => (
- onReactivateStrategy(strategy)}
- permission={UPDATE_STRATEGY}
- tooltipProps={{ title: 'Reactivate activation strategy' }}
- >
-
-
- );
-
- const deprecateButton = (strategy: IStrategy) => (
-
-
-
-
-
-
-
- }
- elseShow={
-
-
onDeprecateStrategy(strategy)}
- permission={UPDATE_STRATEGY}
- tooltipProps={{ title: 'Deprecate strategy' }}
- >
-
-
-
- }
- />
- );
-
- const editButton = (strategy: IStrategy) => (
-
- navigate(`/strategies/${strategy?.name}/edit`)
- }
- permission={UPDATE_STRATEGY}
- tooltipProps={{ title: 'Edit strategy' }}
- >
-
-
- }
- elseShow={
-
-
-
-
-
-
-
- }
- />
- );
-
- const deleteButton = (strategy: IStrategy) => (
- onDeleteStrategy(strategy)}
- permission={DELETE_STRATEGY}
- tooltipProps={{ title: 'Delete strategy' }}
- >
-
-
- }
- elseShow={
-
-
-
-
-
-
-
- }
- />
- );
-
const onDialogConfirm = () => {
dialogueMetaData?.onConfirm();
setDialogueMetaData((prev: IDialogueMetaData) => ({
diff --git a/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx b/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx
new file mode 100644
index 0000000000..6e7ffd092d
--- /dev/null
+++ b/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx
@@ -0,0 +1,41 @@
+import { VFC } from 'react';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
+import { Delete } from '@mui/icons-material';
+import { IconButton, Tooltip } from '@mui/material';
+import { IStrategy } from 'interfaces/strategy';
+import { DELETE_STRATEGY } from 'component/providers/AccessProvider/permissions';
+
+interface IStrategyDeleteButtonProps {
+ strategy: IStrategy;
+ onClick: () => void;
+}
+
+export const StrategyDeleteButton: VFC = ({
+ strategy,
+ onClick,
+}) => {
+ return (
+
+
+
+ }
+ elseShow={
+
+
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx b/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx
new file mode 100644
index 0000000000..a6cad7129c
--- /dev/null
+++ b/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx
@@ -0,0 +1,39 @@
+import { VFC } from 'react';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
+import { Edit } from '@mui/icons-material';
+import { IconButton, Tooltip } from '@mui/material';
+import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
+import { IStrategy } from 'interfaces/strategy';
+
+interface IStrategyEditButtonProps {
+ strategy: IStrategy;
+ onClick: () => void;
+}
+
+export const StrategyEditButton: VFC = ({
+ strategy,
+ onClick,
+}) => (
+
+
+
+ }
+ elseShow={
+
+
+
+
+
+
+
+ }
+ />
+);
diff --git a/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx b/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx
new file mode 100644
index 0000000000..21ef30bba9
--- /dev/null
+++ b/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx
@@ -0,0 +1,43 @@
+import { VFC } from 'react';
+import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
+import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
+import { Tooltip } from '@mui/material';
+import { useId } from 'hooks/useId';
+
+interface IStrategySwitchProps {
+ deprecated: boolean;
+ onToggle: (state: boolean) => void;
+ disabled?: boolean;
+}
+
+export const StrategySwitch: VFC = ({
+ deprecated,
+ disabled,
+ onToggle,
+}) => {
+ const onClick = () => {
+ onToggle(deprecated);
+ };
+ const id = useId();
+
+ const title = deprecated
+ ? 'Excluded from strategy list'
+ : 'Included in strategy list';
+
+ return (
+
+
+
+ );
+};
diff --git a/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts
index 3e406798a2..e2c1cc052b 100644
--- a/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts
+++ b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts
@@ -1,83 +1,94 @@
import { IStrategyPayload } from 'interfaces/strategy';
+import { useCallback } from 'react';
import useAPI from '../useApi/useApi';
+const URI = 'api/admin/strategies';
+
const useStrategiesApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});
- const URI = 'api/admin/strategies';
- const createStrategy = async (strategy: IStrategyPayload) => {
- const req = createRequest(URI, {
- method: 'POST',
- body: JSON.stringify(strategy),
- });
+ const createStrategy = useCallback(
+ async (strategy: IStrategyPayload) => {
+ const req = createRequest(URI, {
+ method: 'POST',
+ body: JSON.stringify(strategy),
+ });
- try {
- const res = await makeRequest(req.caller, req.id);
+ return makeRequest(req.caller, req.id);
+ },
+ [createRequest, makeRequest]
+ );
- return res;
- } catch (e) {
- throw e;
- }
- };
+ const updateStrategy = useCallback(
+ async (strategy: IStrategyPayload) => {
+ const path = `${URI}/${strategy.name}`;
+ const req = createRequest(path, {
+ method: 'PUT',
+ body: JSON.stringify(strategy),
+ });
- const updateStrategy = async (strategy: IStrategyPayload) => {
- const path = `${URI}/${strategy.name}`;
- const req = createRequest(path, {
- method: 'PUT',
- body: JSON.stringify(strategy),
- });
+ try {
+ const res = await makeRequest(req.caller, req.id);
- try {
- const res = await makeRequest(req.caller, req.id);
+ return res;
+ } catch (e) {
+ throw e;
+ }
+ },
+ [createRequest, makeRequest]
+ );
- return res;
- } catch (e) {
- throw e;
- }
- };
+ const removeStrategy = useCallback(
+ async (strategy: IStrategyPayload) => {
+ const path = `${URI}/${strategy.name}`;
+ const req = createRequest(path, { method: 'DELETE' });
- const removeStrategy = async (strategy: IStrategyPayload) => {
- const path = `${URI}/${strategy.name}`;
- const req = createRequest(path, { method: 'DELETE' });
+ try {
+ const res = await makeRequest(req.caller, req.id);
- try {
- const res = await makeRequest(req.caller, req.id);
+ return res;
+ } catch (e) {
+ throw e;
+ }
+ },
+ [createRequest, makeRequest]
+ );
- return res;
- } catch (e) {
- throw e;
- }
- };
+ const deprecateStrategy = useCallback(
+ async (strategy: IStrategyPayload) => {
+ const path = `${URI}/${strategy.name}/deprecate`;
+ const req = createRequest(path, {
+ method: 'POST',
+ });
- const deprecateStrategy = async (strategy: IStrategyPayload) => {
- const path = `${URI}/${strategy.name}/deprecate`;
- const req = createRequest(path, {
- method: 'POST',
- });
+ try {
+ const res = await makeRequest(req.caller, req.id);
- try {
- const res = await makeRequest(req.caller, req.id);
+ return res;
+ } catch (e) {
+ throw e;
+ }
+ },
+ [createRequest, makeRequest]
+ );
- return res;
- } catch (e) {
- throw e;
- }
- };
+ const reactivateStrategy = useCallback(
+ async (strategy: IStrategyPayload) => {
+ const path = `${URI}/${strategy.name}/reactivate`;
+ const req = createRequest(path, { method: 'POST' });
- const reactivateStrategy = async (strategy: IStrategyPayload) => {
- const path = `${URI}/${strategy.name}/reactivate`;
- const req = createRequest(path, { method: 'POST' });
+ try {
+ const res = await makeRequest(req.caller, req.id);
- try {
- const res = await makeRequest(req.caller, req.id);
-
- return res;
- } catch (e) {
- throw e;
- }
- };
+ return res;
+ } catch (e) {
+ throw e;
+ }
+ },
+ [createRequest, makeRequest]
+ );
return {
createStrategy,