diff --git a/frontend/package.json b/frontend/package.json
index 1f14a8a9cb..7832cbe178 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "unleash-frontend",
"description": "unleash your features",
- "version": "4.7.2",
+ "version": "4.8.0-beta.1",
"keywords": [
"unleash",
"feature toggle",
@@ -54,7 +54,6 @@
"@types/react-test-renderer": "17.0.1",
"@types/react-timeago": "4.1.3",
"@welldone-software/why-did-you-render": "6.2.3",
- "array-move": "3.0.1",
"classnames": "2.3.1",
"copy-to-clipboard": "3.3.1",
"craco": "0.0.3",
@@ -64,13 +63,10 @@
"debounce": "1.2.1",
"deep-diff": "1.0.2",
"fast-json-patch": "3.1.0",
- "fetch-mock": "9.11.0",
"http-proxy-middleware": "2.0.2",
- "immutable": "4.0.0",
"@types/lodash.clonedeep": "4.5.6",
"lodash.clonedeep": "4.5.0",
"lodash.flow": "3.5.0",
- "node-fetch": "2.6.7",
"prettier": "2.5.1",
"prop-types": "15.8.1",
"react": "17.0.2",
@@ -79,15 +75,10 @@
"react-dom": "17.0.2",
"react-hooks-global-state": "1.0.2",
"react-outside-click-handler": "1.3.0",
- "react-redux": "7.2.6",
"react-router-dom": "5.3.0",
"react-scripts": "4.0.3",
"react-test-renderer": "16.14.0",
"react-timeago": "6.2.1",
- "redux": "4.1.2",
- "redux-devtools-extension": "2.13.9",
- "redux-mock-store": "1.5.4",
- "redux-thunk": "2.4.1",
"sass": "1.49.7",
"swr": "1.2.1",
"typescript": "4.5.5",
diff --git a/frontend/src/accessStoreFake.js b/frontend/src/accessStoreFake.js
deleted file mode 100644
index fb39464765..0000000000
--- a/frontend/src/accessStoreFake.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Map as $MAp } from 'immutable';
-
-export const createFakeStore = permissions => {
- return {
- getState: () => ({
- user: new $MAp({
- permissions,
- }),
- }),
- };
-};
diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx
index 62ff747de1..80dfd6e2d3 100644
--- a/frontend/src/component/App.tsx
+++ b/frontend/src/component/App.tsx
@@ -9,18 +9,12 @@ import SWRProvider from './providers/SWRProvider/SWRProvider';
import ToastRenderer from './common/ToastRenderer/ToastRenderer';
import styles from './styles.module.scss';
import { Redirect, Route, Switch } from 'react-router-dom';
-import { RouteComponentProps } from 'react-router';
import { routes } from './menu/routes';
-import { useEffect } from 'react';
import { useAuthDetails } from '../hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from '../hooks/api/getters/useAuth/useAuthUser';
import { useAuthSplash } from '../hooks/api/getters/useAuth/useAuthSplash';
-interface IAppProps extends RouteComponentProps {
- fetchUiBootstrap: () => void;
-}
-
-export const App = ({ fetchUiBootstrap }: IAppProps) => {
+export const App = () => {
const { splash, refetchSplash } = useAuthSplash();
const { authDetails } = useAuthDetails();
const { user } = useAuthUser();
@@ -29,10 +23,6 @@ export const App = ({ fetchUiBootstrap }: IAppProps) => {
const hasFetchedAuth = Boolean(authDetails || user);
const showEnvSplash = isLoggedIn && splash?.environment === false;
- useEffect(() => {
- fetchUiBootstrap();
- }, [fetchUiBootstrap, authDetails?.type]);
-
const renderMainLayoutRoutes = () => {
return routes.filter(route => route.layout === 'main').map(renderRoute);
};
diff --git a/frontend/src/component/AppContainer.tsx b/frontend/src/component/AppContainer.tsx
deleted file mode 100644
index 7a0958fa6e..0000000000
--- a/frontend/src/component/AppContainer.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { connect } from 'react-redux';
-import { App } from './App';
-import { fetchUiBootstrap } from '../store/ui-bootstrap/actions';
-
-export default connect(null, { fetchUiBootstrap })(App);
diff --git a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap
new file mode 100644
index 0000000000..55a4015dff
--- /dev/null
+++ b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap
@@ -0,0 +1,64 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly if no application 1`] = `
+
+`;
+
+exports[`renders correctly with permissions 1`] = `
+
+`;
+
+exports[`renders correctly without permission 1`] = `
+
+`;
diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js
new file mode 100644
index 0000000000..3cc15544be
--- /dev/null
+++ b/frontend/src/component/application/__tests__/application-edit-component-test.js
@@ -0,0 +1,158 @@
+import { ThemeProvider } from '@material-ui/core';
+import { ApplicationEdit } from '../ApplicationEdit/ApplicationEdit';
+import renderer from 'react-test-renderer';
+import { MemoryRouter } from 'react-router-dom';
+import { ADMIN } from '../../providers/AccessProvider/permissions';
+import theme from '../../../themes/main-theme';
+import AccessProvider from '../../providers/AccessProvider/AccessProvider';
+import UIProvider from '../../providers/UIProvider/UIProvider';
+
+test('renders correctly if no application', () => {
+ const tree = renderer
+ .create(
+
+
+
+
+ Promise.resolve({})}
+ storeApplicationMetaData={jest.fn()}
+ deleteApplication={jest.fn()}
+ history={{}}
+ locationSettings={{ locale: 'en-GB' }}
+ />
+
+
+
+
+ )
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
+
+test('renders correctly without permission', () => {
+ const tree = renderer
+ .create(
+
+
+
+
+ Promise.resolve({})}
+ storeApplicationMetaData={jest.fn()}
+ deleteApplication={jest.fn()}
+ history={{}}
+ application={{
+ appName: 'test-app',
+ instances: [
+ {
+ instanceId: 'instance-1',
+ clientIp: '123.123.123.123',
+ lastSeen: '2017-02-23T15:56:49',
+ sdkVersion: '4.0',
+ },
+ ],
+ strategies: [
+ {
+ name: 'StrategyA',
+ description: 'A description',
+ },
+ {
+ name: 'StrategyB',
+ description: 'B description',
+ notFound: true,
+ },
+ ],
+ seenToggles: [
+ {
+ name: 'ToggleA',
+ description: 'this is A toggle',
+ enabled: true,
+ project: 'default',
+ },
+ {
+ name: 'ToggleB',
+ description: 'this is B toggle',
+ enabled: false,
+ notFound: true,
+ project: 'default',
+ },
+ ],
+ url: 'http://example.org',
+ description: 'app description',
+ }}
+ locationSettings={{ locale: 'en-GB' }}
+ />
+
+
+
+
+ )
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
+
+test('renders correctly with permissions', () => {
+ const tree = renderer
+ .create(
+
+
+
+
+ Promise.resolve({})}
+ storeApplicationMetaData={jest.fn()}
+ history={{}}
+ deleteApplication={jest.fn()}
+ application={{
+ appName: 'test-app',
+ instances: [
+ {
+ instanceId: 'instance-1',
+ clientIp: '123.123.123.123',
+ lastSeen: '2017-02-23T15:56:49',
+ sdkVersion: '4.0',
+ },
+ ],
+ strategies: [
+ {
+ name: 'StrategyA',
+ description: 'A description',
+ },
+ {
+ name: 'StrategyB',
+ description: 'B description',
+ notFound: true,
+ },
+ ],
+ seenToggles: [
+ {
+ name: 'ToggleA',
+ description: 'this is A toggle',
+ enabled: true,
+ project: 'default',
+ },
+ {
+ name: 'ToggleB',
+ description: 'this is B toggle',
+ enabled: false,
+ notFound: true,
+ project: 'default',
+ },
+ ],
+ url: 'http://example.org',
+ description: 'app description',
+ }}
+ locationSettings={{ locale: 'en-GB' }}
+ />
+
+
+
+
+ )
+ .toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.jsx b/frontend/src/component/application/__tests__/application-edit-component-test.jsx
index 7a48f3e00a..3cc15544be 100644
--- a/frontend/src/component/application/__tests__/application-edit-component-test.jsx
+++ b/frontend/src/component/application/__tests__/application-edit-component-test.jsx
@@ -4,14 +4,13 @@ import renderer from 'react-test-renderer';
import { MemoryRouter } from 'react-router-dom';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import theme from '../../../themes/main-theme';
-import { createFakeStore } from '../../../accessStoreFake';
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
import UIProvider from '../../providers/UIProvider/UIProvider';
test('renders correctly if no application', () => {
const tree = renderer
.create(
-
+
@@ -38,7 +37,7 @@ test('renders correctly without permission', () => {
-
+
Promise.resolve({})}
storeApplicationMetaData={jest.fn()}
@@ -101,9 +100,7 @@ test('renders correctly with permissions', () => {
-
+
Promise.resolve({})}
storeApplicationMetaData={jest.fn()}
diff --git a/frontend/src/component/error/error-component.jsx b/frontend/src/component/error/error-component.jsx
deleted file mode 100644
index 45e0d62e8b..0000000000
--- a/frontend/src/component/error/error-component.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { Snackbar, IconButton } from '@material-ui/core';
-import { Close, QuestionAnswer } from '@material-ui/icons';
-
-const ErrorComponent = ({ errors, muteError }) => {
- const showError = errors.length > 0;
- const error = showError ? errors[0] : undefined;
- return (
-
-
-
-
-
- }
- open={showError}
- onClose={() => muteError(error)}
- autoHideDuration={10000}
- message={
-
-
- {error}
-
- }
- />
- );
-};
-
-ErrorComponent.propTypes = {
- errors: PropTypes.array.isRequired,
- muteError: PropTypes.func.isRequired,
-};
-
-export default ErrorComponent;
diff --git a/frontend/src/component/error/error-container.jsx b/frontend/src/component/error/error-container.jsx
deleted file mode 100644
index 6c8fca1ab8..0000000000
--- a/frontend/src/component/error/error-container.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { connect } from 'react-redux';
-import ErrorComponent from './error-component';
-import { muteError } from '../../store/error/actions';
-
-const mapDispatchToProps = {
- muteError,
-};
-
-const mapStateToProps = state => {
- return {
- errors: state.error
- .get('list')
- .toArray()
- .reverse()
- }
-
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(ErrorComponent);
diff --git a/frontend/src/component/feature/CopyFeature/CopyFeature.jsx b/frontend/src/component/feature/CopyFeature/CopyFeature.jsx
index 88a119219e..208e4db37a 100644
--- a/frontend/src/component/feature/CopyFeature/CopyFeature.jsx
+++ b/frontend/src/component/feature/CopyFeature/CopyFeature.jsx
@@ -1,7 +1,6 @@
-import React, { useState, useRef, useEffect } from 'react';
-import PropTypes from 'prop-types';
+import { useState, useRef, useEffect } from 'react';
-import { Link, useParams } from 'react-router-dom';
+import { Link, useHistory, useParams } from 'react-router-dom';
import {
Button,
@@ -23,17 +22,18 @@ import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureAp
import useFeature from '../../../hooks/api/getters/useFeature/useFeature';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
-const CopyFeature = props => {
+export const CopyFeatureToggle = () => {
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
const [replaceGroupId, setReplaceGroupId] = useState(true);
const [apiError, setApiError] = useState('');
const [nameError, setNameError] = useState(undefined);
const [newToggleName, setNewToggleName] = useState();
- const { cloneFeatureToggle } = useFeatureApi();
+ const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi();
const inputRef = useRef();
const { name: copyToggleName, id: projectId } = useParams();
const { feature } = useFeature(projectId, copyToggleName);
const { uiConfig } = useUiConfig();
+ const history = useHistory();
useEffect(() => {
inputRef.current?.focus();
@@ -50,7 +50,7 @@ const CopyFeature = props => {
const onValidateName = async () => {
try {
- await props.validateName(newToggleName);
+ await validateFeatureToggleName(newToggleName);
setNameError(undefined);
} catch (err) {
@@ -70,7 +70,7 @@ const CopyFeature = props => {
name: newToggleName,
replaceGroupId,
});
- props.history.push(
+ history.push(
getTogglePath(projectId, newToggleName, uiConfig.flags.E)
);
} catch (e) {
@@ -137,10 +137,3 @@ const CopyFeature = props => {
);
};
-
-CopyFeature.propTypes = {
- history: PropTypes.object.isRequired,
- validateName: PropTypes.func.isRequired,
-};
-
-export default CopyFeature;
diff --git a/frontend/src/component/feature/CopyFeature/index.jsx b/frontend/src/component/feature/CopyFeature/index.jsx
deleted file mode 100644
index d54fc2b9c6..0000000000
--- a/frontend/src/component/feature/CopyFeature/index.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { connect } from 'react-redux';
-import CopyFeatureComponent from './CopyFeature';
-import { validateName } from '../../../store/feature-toggle/actions';
-
-const mapStateToProps = (state, props) => ({
- history: props.history,
-});
-
-const mapDispatchToProps = dispatch => ({
- validateName,
-});
-
-const FormAddContainer = connect(
- mapStateToProps,
- mapDispatchToProps
-)(CopyFeatureComponent);
-
-export default FormAddContainer;
diff --git a/frontend/src/component/layout/MainLayout/MainLayout.tsx b/frontend/src/component/layout/MainLayout/MainLayout.tsx
index 525c9e225a..19a7b0e087 100644
--- a/frontend/src/component/layout/MainLayout/MainLayout.tsx
+++ b/frontend/src/component/layout/MainLayout/MainLayout.tsx
@@ -3,7 +3,6 @@ import classnames from 'classnames';
import { makeStyles } from '@material-ui/core/styles';
import { Grid } from '@material-ui/core';
import styles from '../../styles.module.scss';
-import ErrorContainer from '../../error/error-container';
import Header from '../../menu/Header/Header';
import Footer from '../../menu/Footer/Footer';
import Proclamation from '../../common/Proclamation/Proclamation';
@@ -27,7 +26,7 @@ const useStyles = makeStyles(theme => ({
}));
interface IMainLayoutProps {
- children: ReactNode
+ children: ReactNode;
}
export const MainLayout = ({ children }: IMainLayoutProps) => {
@@ -48,7 +47,6 @@ export const MainLayout = ({ children }: IMainLayoutProps) => {
{children}
-
{
if (!permission) {
- console.warn(`Missing permission for AccessProvider: ${permission}`)
- return false
+ console.warn(`Missing permission for AccessProvider: ${permission}`);
+ return false;
}
if (p.permission === ADMIN) {
diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
index af59195c0d..c3cceed402 100644
--- a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
+++ b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap
@@ -28,7 +28,51 @@ exports[`renders correctly with one strategy 1`] = `
+ >
+
+
+
+
+
@@ -162,7 +253,51 @@ exports[`renders correctly with one strategy without permissions 1`] = `
+ >
+
+
+
+
+
diff --git a/frontend/src/component/strategies/__tests__/list-component-test.jsx b/frontend/src/component/strategies/__tests__/list-component-test.jsx
index 801eb528b3..3258a44810 100644
--- a/frontend/src/component/strategies/__tests__/list-component-test.jsx
+++ b/frontend/src/component/strategies/__tests__/list-component-test.jsx
@@ -4,7 +4,6 @@ import { StrategiesList } from '../StrategiesList/StrategiesList';
import renderer from 'react-test-renderer';
import theme from '../../../themes/main-theme';
import AccessProvider from '../../providers/AccessProvider/AccessProvider';
-import { createFakeStore } from '../../../accessStoreFake';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import UIProvider from '../../providers/UIProvider/UIProvider';
@@ -17,7 +16,7 @@ test('renders correctly with one strategy', () => {
-
+
{
-
+
>;
diff --git a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
index f7b157eb14..522bab536e 100644
--- a/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
+++ b/frontend/src/hooks/api/actions/useAdminUsersApi/errorHandlers.ts
@@ -4,7 +4,7 @@ import {
AuthenticationError,
ForbiddenError,
NotFoundError,
-} from '../../../../store/api-helper';
+} from '../../../../utils/api-utils';
export const handleBadRequest = async (
setErrors?: Dispatch>,
diff --git a/frontend/src/hooks/api/actions/useApi/useApi.ts b/frontend/src/hooks/api/actions/useApi/useApi.ts
index eb2157a278..653315aaec 100644
--- a/frontend/src/hooks/api/actions/useApi/useApi.ts
+++ b/frontend/src/hooks/api/actions/useApi/useApi.ts
@@ -12,7 +12,7 @@ import {
ForbiddenError,
headers,
NotFoundError,
-} from '../../../../store/api-helper';
+} from '../../../../utils/api-utils';
import { formatApiPath } from '../../../../utils/format-path';
interface IUseAPI {
diff --git a/frontend/src/hooks/api/actions/useAuthApi/useAuthApi.tsx b/frontend/src/hooks/api/actions/useAuthApi/useAuthApi.tsx
index 9e5e2bb907..555b6ddc42 100644
--- a/frontend/src/hooks/api/actions/useAuthApi/useAuthApi.tsx
+++ b/frontend/src/hooks/api/actions/useAuthApi/useAuthApi.tsx
@@ -1,3 +1,4 @@
+import { headers } from '../../../../utils/api-utils';
import useAPI from '../useApi/useApi';
type PasswordLogin = (
@@ -16,24 +17,36 @@ interface IUseAuthApiOutput {
}
export const useAuthApi = (): IUseAuthApiOutput => {
- const { makeRequest, createRequest, errors, loading } = useAPI({
+ const { makeRequest, errors, loading } = useAPI({
propagateErrors: true,
});
const passwordAuth = (path: string, username: string, password: string) => {
- const req = createRequest(ensureRelativePath(path), {
- method: 'POST',
- body: JSON.stringify({ username, password }),
- });
+ const req = {
+ caller: () => {
+ return fetch(path, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify({ username, password }),
+ });
+ },
+ id: 'passwordAuth',
+ };
return makeRequest(req.caller, req.id);
};
const emailAuth = (path: string, email: string) => {
- const req = createRequest(ensureRelativePath(path), {
- method: 'POST',
- body: JSON.stringify({ email }),
- });
+ const req = {
+ caller: () => {
+ return fetch(path, {
+ headers,
+ method: 'POST',
+ body: JSON.stringify({ email }),
+ });
+ },
+ id: 'emailAuth',
+ };
return makeRequest(req.caller, req.id);
};
diff --git a/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts b/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts
index 9531cbdb9c..2f3e6cd1b9 100644
--- a/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts
+++ b/frontend/src/hooks/api/getters/useUiBootstrap/useUiBootstrap.ts
@@ -4,6 +4,8 @@ import { useState, useEffect } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
const useUiBootstrap = (options: SWRConfiguration = {}) => {
+ // The point of the bootstrap is to get multiple datasets in one call. Therefore,
+ // this needs to be refactored to seed other hooks with the correct data.
const BOOTSTRAP_CACHE_KEY = `api/admin/ui-bootstrap`;
const fetcher = () => {
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 02ae50648c..4731d0294b 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -5,58 +5,34 @@ import './app.css';
import ReactDOM from 'react-dom';
import { Route, BrowserRouter as Router } from 'react-router-dom';
-import { Provider } from 'react-redux';
import { ThemeProvider, CssBaseline } from '@material-ui/core';
-import thunkMiddleware from 'redux-thunk';
-import { createStore, applyMiddleware, compose } from 'redux';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { StylesProvider } from '@material-ui/core/styles';
import mainTheme from './themes/main-theme';
-import store from './store';
-import App from './component/AppContainer';
+import { App } from './component/App';
import ScrollToTop from './component/scroll-to-top';
-import { writeWarning } from './security-logger';
import AccessProvider from './component/providers/AccessProvider/AccessProvider';
import { getBasePath } from './utils/format-path';
import UIProvider from './component/providers/UIProvider/UIProvider';
-let composeEnhancers;
-
-if (
- process.env.NODE_ENV !== 'production' &&
- (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
-) {
- composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
-} else {
- composeEnhancers = compose;
- writeWarning();
-}
-
-const unleashStore = createStore(
- store,
- composeEnhancers(applyMiddleware(thunkMiddleware))
-);
-
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
document.getElementById('app')
);
diff --git a/frontend/src/page/features/copy.js b/frontend/src/page/features/copy.js
deleted file mode 100644
index 7afe17a487..0000000000
--- a/frontend/src/page/features/copy.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import CopyFeatureToggleForm from '../../component/feature/CopyFeature';
-import PropTypes from 'prop-types';
-
-const render = ({ history, match: { params } }) => (
-
-);
-
-render.propTypes = {
- history: PropTypes.object.isRequired,
- match: PropTypes.object.isRequired,
-};
-
-export default render;
diff --git a/frontend/src/store/api-calls/index.js b/frontend/src/store/api-calls/index.js
deleted file mode 100644
index d748d4232a..0000000000
--- a/frontend/src/store/api-calls/index.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- START_FETCH_FEATURE_TOGGLES,
- FETCH_FEATURE_TOGGLES_SUCCESS,
- FETCH_FEATURE_TOGGLE_ERROR,
- RESET_LOADING,
-} from '../feature-toggle/actions';
-
-const apiCalls = (
- state = {
- fetchTogglesState: {
- loading: false,
- success: false,
- error: null,
- count: 0,
- },
- },
- action
-) => {
- switch (action.type) {
- case START_FETCH_FEATURE_TOGGLES:
- if (state.fetchTogglesState.count > 0) return state;
- return {
- ...state,
- fetchTogglesState: {
- ...state.fetchTogglesState,
- loading: true,
- success: false,
- error: null,
- },
- };
- case FETCH_FEATURE_TOGGLES_SUCCESS:
- return {
- ...state,
- fetchTogglesState: {
- ...state.fetchTogglesState,
- loading: false,
- success: true,
- error: null,
- count: (state.fetchTogglesState.count += 1),
- },
- };
- case FETCH_FEATURE_TOGGLE_ERROR:
- return {
- ...state,
- fetchTogglesState: {
- ...state.fetchTogglesState,
- loading: false,
- success: false,
- error: true,
- },
- };
- case RESET_LOADING:
- return {
- ...state,
- fetchTogglesState: { ...state.fetchTogglesState, count: 0 },
- };
- default:
- return state;
- }
-};
-
-export default apiCalls;
diff --git a/frontend/src/store/api-helper.js b/frontend/src/store/api-helper.js
deleted file mode 100644
index e528135295..0000000000
--- a/frontend/src/store/api-helper.js
+++ /dev/null
@@ -1,110 +0,0 @@
-const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
-
-function extractJoiMsg(body) {
- return body.details.length > 0
- ? body.details[0].message
- : defaultErrorMessage;
-}
-function extractLegacyMsg(body) {
- return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
-}
-
-class ServiceError extends Error {
- constructor(statusCode = 500) {
- super(defaultErrorMessage);
- this.name = 'ServiceError';
- this.statusCode = statusCode;
- }
-}
-
-export class AuthenticationError extends Error {
- constructor(statusCode, body) {
- super('Authentication required');
- this.name = 'AuthenticationError';
- this.statusCode = statusCode;
- this.body = body;
- }
-}
-
-export class ForbiddenError extends Error {
- constructor(statusCode, body = {}) {
- super(
- body.details?.length > 0
- ? body.details[0].message
- : 'You cannot perform this action'
- );
- this.name = 'ForbiddenError';
- this.statusCode = statusCode;
- this.body = body;
- }
-}
-
-export class BadRequestError extends Error {
- constructor(statusCode, body = {}) {
- super(
- body.details?.length > 0 ? body.details[0].message : 'Bad request'
- );
- this.name = 'BadRequestError';
- this.statusCode = statusCode;
- this.body = body;
- }
-}
-
-export class NotFoundError extends Error {
- constructor(statusCode) {
- super(
- 'The requested resource could not be found but may be available in the future'
- );
- this.name = 'NotFoundError';
- this.statusCode = statusCode;
- }
-}
-
-export function throwIfNotSuccess(response) {
- if (!response.ok) {
- if (response.status === 401) {
- return new Promise((resolve, reject) => {
- response
- .json()
- .then(body =>
- reject(new AuthenticationError(response.status, body))
- );
- });
- } else if (response.status === 403) {
- return new Promise((resolve, reject) => {
- response
- .json()
- .then(body =>
- reject(new ForbiddenError(response.status, body))
- );
- });
- } else if (response.status === 404) {
- return new Promise((resolve, reject) => {
- reject(new NotFoundError(response.status));
- });
- } else if (response.status > 399 && response.status < 501) {
- return new Promise((resolve, reject) => {
- response
- .json()
- .then(body => {
- const errorMsg =
- body && body.isJoi
- ? extractJoiMsg(body)
- : extractLegacyMsg(body);
- let error = new Error(errorMsg);
- error.statusCode = response.status;
- reject(error);
- })
- .catch(() => reject(new Error(defaultErrorMessage)));
- });
- } else {
- return Promise.reject(new ServiceError(response.status));
- }
- }
- return Promise.resolve(response);
-}
-
-export const headers = {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
-};
diff --git a/frontend/src/store/error/actions.js b/frontend/src/store/error/actions.js
deleted file mode 100644
index 3d378e690e..0000000000
--- a/frontend/src/store/error/actions.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export const MUTE_ERRORS = 'MUTE_ERRORS';
-export const MUTE_ERROR = 'MUTE_ERROR';
-
-export const muteErrors = () => ({ type: MUTE_ERRORS });
-
-export const muteError = error => ({ type: MUTE_ERROR, error });
diff --git a/frontend/src/store/error/index.js b/frontend/src/store/error/index.js
deleted file mode 100644
index ec0cc63798..0000000000
--- a/frontend/src/store/error/index.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { List, Map as $Map } from 'immutable';
-import { MUTE_ERROR } from './actions';
-import {
- ERROR_FETCH_FEATURE_TOGGLES,
- ERROR_CREATING_FEATURE_TOGGLE,
- ERROR_REMOVE_FEATURE_TOGGLE,
- ERROR_UPDATE_FEATURE_TOGGLE,
- UPDATE_FEATURE_TOGGLE_STRATEGIES,
- UPDATE_FEATURE_TOGGLE,
-} from '../feature-toggle/actions';
-
-import { FORBIDDEN } from '../util';
-
-const debug = require('debug')('unleash:error-store');
-
-function getInitState() {
- return new $Map({
- list: new List(),
- });
-}
-
-function addErrorIfNotAlreadyInList(state, error) {
- debug('Got error', error);
- if (state.get('list').indexOf(error) < 0) {
- return state.update('list', list => list.push(error));
- }
- return state;
-}
-
-const strategies = (state = getInitState(), action) => {
- switch (action.type) {
- case ERROR_CREATING_FEATURE_TOGGLE:
- case ERROR_REMOVE_FEATURE_TOGGLE:
- case ERROR_FETCH_FEATURE_TOGGLES:
- case ERROR_UPDATE_FEATURE_TOGGLE:
- return addErrorIfNotAlreadyInList(state, action.error.message);
- case FORBIDDEN:
- return addErrorIfNotAlreadyInList(
- state,
- action.error.message || '403 Forbidden'
- );
- case MUTE_ERROR:
- return state.update('list', list =>
- list.remove(list.indexOf(action.error))
- );
- // This reducer controls not only errors, but general information and success
- // messages. This can be a little misleading, given it's naming. We should
- // revise how this works in a future update.
- case UPDATE_FEATURE_TOGGLE:
- case UPDATE_FEATURE_TOGGLE_STRATEGIES:
- return addErrorIfNotAlreadyInList(state, action.info);
- default:
- return state;
- }
-};
-
-export default strategies;
diff --git a/frontend/src/store/feature-toggle/actions.js b/frontend/src/store/feature-toggle/actions.js
deleted file mode 100644
index 05f4f82637..0000000000
--- a/frontend/src/store/feature-toggle/actions.js
+++ /dev/null
@@ -1,235 +0,0 @@
-import api from './api';
-import { dispatchError } from '../util';
-import { MUTE_ERROR } from '../error/actions';
-const debug = require('debug')('unleash:feature-actions');
-
-export const ADD_FEATURE_TOGGLE = 'ADD_FEATURE_TOGGLE';
-export const COPY_FEATURE_TOGGLE = 'COPY_FEATURE_TOGGLE';
-export const REMOVE_FEATURE_TOGGLE = 'REMOVE_FEATURE_TOGGLE';
-export const UPDATE_FEATURE_TOGGLE = 'UPDATE_FEATURE_TOGGLE';
-export const TOGGLE_FEATURE_TOGGLE = 'TOGGLE_FEATURE_TOGGLE';
-export const START_FETCH_FEATURE_TOGGLES = 'START_FETCH_FEATURE_TOGGLES';
-export const START_UPDATE_FEATURE_TOGGLE = 'START_UPDATE_FEATURE_TOGGLE';
-export const START_CREATE_FEATURE_TOGGLE = 'START_CREATE_FEATURE_TOGGLE';
-export const FETCH_FEATURE_TOGGLES_SUCCESS = 'FETCH_FEATURE_TOGGLES_SUCCESS';
-export const START_REMOVE_FEATURE_TOGGLE = 'START_REMOVE_FEATURE_TOGGLE';
-export const RECEIVE_FEATURE_TOGGLES = 'RECEIVE_FEATURE_TOGGLES';
-export const ERROR_FETCH_FEATURE_TOGGLES = 'ERROR_FETCH_FEATURE_TOGGLES';
-export const ERROR_CREATING_FEATURE_TOGGLE = 'ERROR_CREATING_FEATURE_TOGGLE';
-export const ERROR_UPDATE_FEATURE_TOGGLE = 'ERROR_UPDATE_FEATURE_TOGGLE';
-export const ERROR_REMOVE_FEATURE_TOGGLE = 'ERROR_REMOVE_FEATURE_TOGGLE';
-export const UPDATE_FEATURE_TOGGLE_STRATEGIES =
- 'UPDATE_FEATURE_TOGGLE_STRATEGIES';
-export const FETCH_FEATURE_TOGGLE_ERROR = 'FETCH_FEATURE_TOGGLE_ERROR';
-export const RESET_LOADING = 'RESET_LOADING';
-
-export const RECEIVE_FEATURE_TOGGLE = 'RECEIVE_FEATURE_TOGGLE';
-export const START_FETCH_FEATURE_TOGGLE = 'START_FETCH_FEATURE_TOGGLE';
-export const ERROR_FETCH_FEATURE_TOGGLE = 'START_FETCH_FEATURE_TOGGLE';
-
-export function toggleFeature(enable, name) {
- debug('Toggle feature toggle ', name);
- return dispatch => {
- dispatch(requestToggleFeatureToggle(enable, name));
- };
-}
-
-export function setStale(stale, name) {
- debug('Set stale property on feature toggle ', name);
- return dispatch => {
- dispatch(requestSetStaleFeatureToggle(stale, name));
- };
-}
-
-export function editFeatureToggle(featureToggle) {
- debug('Update feature toggle ', featureToggle);
- return dispatch => {
- dispatch(requestUpdateFeatureToggle(featureToggle));
- };
-}
-
-function receiveFeatureToggles(json) {
- debug('reviced feature toggles', json);
- return {
- type: RECEIVE_FEATURE_TOGGLES,
- featureToggles: json.features.map(features => features),
- receivedAt: Date.now(),
- };
-}
-
-function receiveFeatureToggle(featureToggle) {
- debug('reviced feature toggle', featureToggle);
- return {
- type: RECEIVE_FEATURE_TOGGLE,
- featureToggle,
- receivedAt: Date.now(),
- };
-}
-
-export function fetchFeatureToggles() {
- debug('Start fetching feature toggles');
- return dispatch => {
- dispatch({ type: START_FETCH_FEATURE_TOGGLES });
-
- return api
- .fetchAll()
- .then(json => {
- dispatch({ type: FETCH_FEATURE_TOGGLES_SUCCESS });
- dispatch(receiveFeatureToggles(json));
- })
- .catch(() => {
- dispatch({ type: FETCH_FEATURE_TOGGLE_ERROR });
- dispatchError(dispatch, ERROR_FETCH_FEATURE_TOGGLES);
- });
- };
-}
-
-export function fetchFeatureToggle(name) {
- debug('Start fetching feature toggles');
-
- return dispatch => {
- dispatch({ type: START_FETCH_FEATURE_TOGGLE });
-
- return api
- .fetchFeatureToggle(name)
- .then(json => dispatch(receiveFeatureToggle(json)))
- .catch(dispatchError(dispatch, ERROR_FETCH_FEATURE_TOGGLE));
- };
-}
-
-export function createFeatureToggles(featureToggle) {
- return dispatch => {
- dispatch({ type: START_CREATE_FEATURE_TOGGLE });
-
- return api
- .create(featureToggle)
- .then(res => res.json())
- .then(createdFeature => {
- dispatch({
- type: ADD_FEATURE_TOGGLE,
- featureToggle: createdFeature,
- });
- })
- .catch(e => {
- dispatchError(dispatch, ERROR_CREATING_FEATURE_TOGGLE);
- throw e;
- });
- };
-}
-
-export function requestToggleFeatureToggle(enable, name) {
- return dispatch => {
- dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
-
- return api
- .toggle(enable, name)
- .then(() => dispatch({ type: TOGGLE_FEATURE_TOGGLE, name }))
- .catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
- };
-}
-
-export function requestSetStaleFeatureToggle(stale, name) {
- return dispatch => {
- dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
-
- return api
- .setStale(stale, name)
- .then(featureToggle => {
- const info = `${name} marked as ${stale ? 'Stale' : 'Active'}.`;
- setTimeout(
- () => dispatch({ type: MUTE_ERROR, error: info }),
- 1000
- );
- dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info });
- })
- .catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
- };
-}
-
-export function requestUpdateFeatureToggle(featureToggle) {
- return dispatch => {
- dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
-
- return api
- .update(featureToggle)
- .then(() => {
- const info = `${featureToggle.name} successfully updated!`;
- setTimeout(
- () => dispatch({ type: MUTE_ERROR, error: info }),
- 1000
- );
- dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle, info });
- })
- .catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
- };
-}
-
-export function requestUpdateFeatureToggleStrategies(
- featureToggle,
- newStrategies
-) {
- return dispatch => {
- featureToggle.strategies = newStrategies;
- dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
-
- return api
- .update(featureToggle)
- .then(() => {
- const info = `${featureToggle.name} successfully updated!`;
- setTimeout(
- () => dispatch({ type: MUTE_ERROR, error: info }),
- 1000
- );
- return dispatch({
- type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
- featureToggle,
- info,
- });
- })
- .catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
- };
-}
-
-export function requestUpdateFeatureToggleVariants(featureToggle, newVariants) {
- return dispatch => {
- const newFeature = { ...featureToggle };
- newFeature.variants = newVariants;
- dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
-
- return api
- .update(newFeature)
- .then(() => {
- const info = `${newFeature.name} successfully updated!`;
- setTimeout(
- () => dispatch({ type: MUTE_ERROR, error: info }),
- 1000
- );
- return dispatch({
- type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
- featureToggle: newFeature,
- info,
- });
- })
- .catch(e => {
- dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE);
- throw e;
- });
- };
-}
-
-export function removeFeatureToggle(featureToggleName) {
- return dispatch => {
- dispatch({ type: START_REMOVE_FEATURE_TOGGLE });
-
- return api
- .remove(featureToggleName)
- .then(() =>
- dispatch({ type: REMOVE_FEATURE_TOGGLE, featureToggleName })
- )
- .catch(dispatchError(dispatch, ERROR_REMOVE_FEATURE_TOGGLE));
- };
-}
-
-export function validateName(featureToggleName) {
- return api.validate({ name: featureToggleName });
-}
diff --git a/frontend/src/store/feature-toggle/api.js b/frontend/src/store/feature-toggle/api.js
deleted file mode 100644
index d354d99702..0000000000
--- a/frontend/src/store/feature-toggle/api.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { formatApiPath } from '../../utils/format-path';
-import { throwIfNotSuccess, headers } from '../api-helper';
-
-const URI = formatApiPath('api/admin/features');
-
-function validateToggle(featureToggle) {
- return new Promise((resolve, reject) => {
- if (
- !featureToggle.strategies ||
- featureToggle.strategies.length === 0
- ) {
- reject(new Error('You must add at least one activation strategy'));
- } else {
- resolve(featureToggle);
- }
- });
-}
-
-function fetchAll() {
- return fetch(URI, { credentials: 'include' })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-function fetchFeatureToggle(name) {
- return fetch(`${URI}/${name}`, { credentials: 'include' })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-async function create(featureToggle) {
- await validateToggle(featureToggle);
-
- return fetch(URI, {
- method: 'POST',
- headers,
- credentials: 'include',
- body: JSON.stringify(featureToggle),
- }).then(throwIfNotSuccess);
-}
-
-function validate(featureToggle) {
- return fetch(`${URI}/validate`, {
- method: 'POST',
- headers,
- credentials: 'include',
- body: JSON.stringify(featureToggle),
- }).then(throwIfNotSuccess);
-}
-
-function update(featureToggle) {
- return validateToggle(featureToggle)
- .then(() => {
- return fetch(`${URI}/${featureToggle.name}`, {
- method: 'PUT',
- headers,
- credentials: 'include',
- body: JSON.stringify(featureToggle),
- });
- })
- .then(throwIfNotSuccess);
-}
-
-function toggle(enable, name) {
- const action = enable ? 'on' : 'off';
- return fetch(`${URI}/${name}/toggle/${action}`, {
- method: 'POST',
- headers,
- credentials: 'include',
- }).then(throwIfNotSuccess);
-}
-
-function setStale(isStale, name) {
- const action = isStale ? 'on' : 'off';
- return fetch(`${URI}/${name}/stale/${action}`, {
- method: 'POST',
- headers,
- credentials: 'include',
- })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-function remove(featureToggleName) {
- return fetch(`${URI}/${featureToggleName}`, {
- method: 'DELETE',
- credentials: 'include',
- }).then(throwIfNotSuccess);
-}
-
-export default {
- fetchAll,
- fetchFeatureToggle,
- create,
- validate,
- update,
- toggle,
- setStale,
- remove,
-};
diff --git a/frontend/src/store/feature-toggle/index.js b/frontend/src/store/feature-toggle/index.js
deleted file mode 100644
index 2ae1dbb69a..0000000000
--- a/frontend/src/store/feature-toggle/index.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { List, Map as $Map } from 'immutable';
-import {
- ADD_FEATURE_TOGGLE,
- RECEIVE_FEATURE_TOGGLES,
- RECEIVE_FEATURE_TOGGLE,
- UPDATE_FEATURE_TOGGLE,
- UPDATE_FEATURE_TOGGLE_STRATEGIES,
- REMOVE_FEATURE_TOGGLE,
- TOGGLE_FEATURE_TOGGLE,
-} from './actions';
-
-const debug = require('debug')('unleash:feature-store');
-
-const features = (state = new List([]), action) => {
- switch (action.type) {
- case ADD_FEATURE_TOGGLE:
- debug(ADD_FEATURE_TOGGLE, action);
- return state.push(new $Map(action.featureToggle));
- case REMOVE_FEATURE_TOGGLE:
- debug(REMOVE_FEATURE_TOGGLE, action);
- return state.filter(
- toggle => toggle.get('name') !== action.featureToggleName
- );
- case TOGGLE_FEATURE_TOGGLE:
- debug(TOGGLE_FEATURE_TOGGLE, action);
- return state.map(toggle => {
- if (toggle.get('name') === action.name) {
- return toggle.set('enabled', !toggle.get('enabled'));
- } else {
- return toggle;
- }
- });
- case UPDATE_FEATURE_TOGGLE_STRATEGIES:
- debug(UPDATE_FEATURE_TOGGLE_STRATEGIES, action);
- return state.map(toggle => {
- if (toggle.get('name') === action.featureToggle.name) {
- return new $Map(action.featureToggle);
- } else {
- return toggle;
- }
- });
- case UPDATE_FEATURE_TOGGLE:
- debug(UPDATE_FEATURE_TOGGLE, action);
- return state.map(toggle => {
- if (toggle.get('name') === action.featureToggle.name) {
- return new $Map(action.featureToggle);
- } else {
- return toggle;
- }
- });
- case RECEIVE_FEATURE_TOGGLE:
- debug(RECEIVE_FEATURE_TOGGLE, action);
- return state.map(toggle => {
- if (toggle.get('name') === action.featureToggle.name) {
- return new $Map(action.featureToggle);
- } else {
- return toggle;
- }
- });
- case RECEIVE_FEATURE_TOGGLES:
- debug(RECEIVE_FEATURE_TOGGLES, action);
- return new List(action.featureToggles.map($Map));
- default:
- return state;
- }
-};
-
-export default features;
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
deleted file mode 100644
index 812a7762cd..0000000000
--- a/frontend/src/store/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { combineReducers } from 'redux';
-import features from './feature-toggle';
-import error from './error';
-import apiCalls from './api-calls';
-
-const unleashStore = combineReducers({
- features,
- error,
- apiCalls,
-});
-
-export default unleashStore;
diff --git a/frontend/src/store/ui-bootstrap/actions.js b/frontend/src/store/ui-bootstrap/actions.js
deleted file mode 100644
index 317536c4e9..0000000000
--- a/frontend/src/store/ui-bootstrap/actions.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import api from './api';
-import { dispatchError } from '../util';
-
-export const RECEIVE_BOOTSTRAP = 'RECEIVE_CONFIG';
-export const ERROR_RECEIVE_BOOTSTRAP = 'ERROR_RECEIVE_CONFIG';
-
-export function fetchUiBootstrap() {
- return dispatch =>
- api
- .fetchUIBootstrap()
- .then(json => {})
- .catch(dispatchError(dispatch, ERROR_RECEIVE_BOOTSTRAP));
-}
diff --git a/frontend/src/store/ui-bootstrap/api.js b/frontend/src/store/ui-bootstrap/api.js
deleted file mode 100644
index 98e0207798..0000000000
--- a/frontend/src/store/ui-bootstrap/api.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { formatApiPath } from '../../utils/format-path';
-import { throwIfNotSuccess } from '../api-helper';
-
-const URI = formatApiPath('api/admin/ui-bootstrap');
-
-function fetchUIBootstrap() {
- return fetch(URI, { credentials: 'include' })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-export default {
- fetchUIBootstrap,
-};
diff --git a/frontend/src/store/util.js b/frontend/src/store/util.js
deleted file mode 100644
index 95208f0918..0000000000
--- a/frontend/src/store/util.js
+++ /dev/null
@@ -1,20 +0,0 @@
-export const AUTH_REQUIRED = 'AUTH_REQUIRED';
-export const FORBIDDEN = 'FORBIDDEN';
-
-export function dispatchError(dispatch, type) {
- return error => {
- switch (error.statusCode) {
- case 401:
- dispatch({ type: AUTH_REQUIRED, error, receivedAt: Date.now() });
- break;
- case 403:
- dispatch({ type: FORBIDDEN, error, receivedAt: Date.now() });
- break;
- default:
- dispatch({ type, error, receivedAt: Date.now() });
- break;
- }
- };
-}
-
-export const success = (dispatch, type, val) => value => dispatch({ type, value: val ? val : value });
diff --git a/frontend/src/utils/api-utils.ts b/frontend/src/utils/api-utils.ts
new file mode 100644
index 0000000000..270d6818cc
--- /dev/null
+++ b/frontend/src/utils/api-utils.ts
@@ -0,0 +1,67 @@
+const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
+
+export const headers = {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+};
+
+export const extractJoiMsg = (body: any) => {
+ return body.details.length > 0
+ ? body.details[0].message
+ : defaultErrorMessage;
+};
+
+export const extractLegacyMsg = (body: any[]) => {
+ return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
+};
+
+export class ServiceError extends Error {
+ constructor(statusCode = 500) {
+ super(defaultErrorMessage);
+ this.name = 'ServiceError';
+ this.statusCode = statusCode;
+ }
+}
+
+export class AuthenticationError extends Error {
+ constructor(statusCode, body) {
+ super('Authentication required');
+ this.name = 'AuthenticationError';
+ this.statusCode = statusCode;
+ this.body = body;
+ }
+}
+
+export class ForbiddenError extends Error {
+ constructor(statusCode, body = {}) {
+ super(
+ body.details?.length > 0
+ ? body.details[0].message
+ : 'You cannot perform this action'
+ );
+ this.name = 'ForbiddenError';
+ this.statusCode = statusCode;
+ this.body = body;
+ }
+}
+
+export class BadRequestError extends Error {
+ constructor(statusCode, body = {}) {
+ super(
+ body.details?.length > 0 ? body.details[0].message : 'Bad request'
+ );
+ this.name = 'BadRequestError';
+ this.statusCode = statusCode;
+ this.body = body;
+ }
+}
+
+export class NotFoundError extends Error {
+ constructor(statusCode) {
+ super(
+ 'The requested resource could not be found but may be available in the future'
+ );
+ this.name = 'NotFoundError';
+ this.statusCode = statusCode;
+ }
+}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 2d58650bc8..3f9f53024f 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -55,7 +55,7 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
+"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
version "7.13.14"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.13.14.tgz"
integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==
@@ -1198,14 +1198,14 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.13.10"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.12.13", "@babel/runtime@^7.15.4":
+"@babel/runtime@^7.12.13":
version "7.15.4"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz"
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
@@ -2012,14 +2012,6 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
-"@types/hoist-non-react-statics@^3.3.0":
- version "3.3.1"
- resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz"
- integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
- dependencies:
- "@types/react" "*"
- hoist-non-react-statics "^3.3.0"
-
"@types/html-minifier-terser@^5.0.0":
version "5.1.1"
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz"
@@ -2148,16 +2140,6 @@
dependencies:
"@types/react" "*"
-"@types/react-redux@^7.1.20":
- version "7.1.20"
- resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.20.tgz"
- integrity sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw==
- dependencies:
- "@types/hoist-non-react-statics" "^3.3.0"
- "@types/react" "*"
- hoist-non-react-statics "^3.3.0"
- redux "^4.0.0"
-
"@types/react-router-dom@5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
@@ -2789,7 +2771,7 @@ anymatch@^3.0.3, anymatch@~3.1.1:
anymatch@~3.1.2:
version "3.1.2"
- resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
@@ -2866,11 +2848,6 @@ array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3:
get-intrinsic "^1.1.1"
is-string "^1.0.5"
-array-move@3.0.1:
- version "3.0.1"
- resolved "https://registry.npmjs.org/array-move/-/array-move-3.0.1.tgz"
- integrity sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==
-
array-union@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
@@ -3721,9 +3698,9 @@ check-types@^11.1.1:
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
"chokidar@>=3.0.0 <4.0.0":
- version "3.5.2"
- resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz"
- integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
@@ -4151,7 +4128,7 @@ core-js@^2.4.0:
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-core-js@^3.0.0, core-js@^3.6.5:
+core-js@^3.6.5:
version "3.10.0"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.10.0.tgz"
integrity sha512-MQx/7TLgmmDVamSyfE+O+5BHvG1aUGj/gHhLn1wVtm2B5u1eVIPvh7vkfjwWKNCjrTJB8+He99IntSQ1qP+vYQ==
@@ -5702,22 +5679,6 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
-fetch-mock@9.11.0:
- version "9.11.0"
- resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz"
- integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==
- dependencies:
- "@babel/core" "^7.0.0"
- "@babel/runtime" "^7.0.0"
- core-js "^3.0.0"
- debug "^4.1.1"
- glob-to-regexp "^0.4.0"
- is-subset "^0.1.1"
- lodash.isequal "^4.5.0"
- path-to-regexp "^2.2.1"
- querystring "^0.2.0"
- whatwg-url "^6.5.0"
-
figgy-pudding@^3.5.1:
version "3.5.2"
resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz"
@@ -6078,11 +6039,6 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
-glob-to-regexp@^0.4.0:
- version "0.4.1"
- resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz"
- integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
@@ -6319,7 +6275,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -6565,9 +6521,9 @@ immer@8.0.1:
resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz"
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
-immutable@4.0.0, immutable@^4.0.0:
+immutable@^4.0.0:
version "4.0.0"
- resolved "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
import-cwd@^2.0.0:
@@ -7045,11 +7001,6 @@ is-string@^1.0.5:
resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz"
integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
-is-subset@^0.1.1:
- version "0.1.1"
- resolved "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz"
- integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=
-
is-svg@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz"
@@ -8026,16 +7977,6 @@ lodash.flow@3.5.0:
resolved "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz"
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
-lodash.isequal@^4.5.0:
- version "4.5.0"
- resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz"
- integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
-
-lodash.isplainobject@^4.0.6:
- version "4.0.6"
- resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz"
- integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
-
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
@@ -8046,11 +7987,6 @@ lodash.once@^4.1.1:
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
-lodash.sortby@^4.7.0:
- version "4.7.0"
- resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz"
- integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-
lodash.template@^4.5.0:
version "4.5.0"
resolved "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz"
@@ -8549,13 +8485,6 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
-node-fetch@2.6.7:
- version "2.6.7"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
- integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
- dependencies:
- whatwg-url "^5.0.0"
-
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz"
@@ -9111,11 +9040,6 @@ path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
-path-to-regexp@^2.2.1:
- version "2.4.0"
- resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz"
- integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
-
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
@@ -10327,7 +10251,7 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-i
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
+"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -10343,18 +10267,6 @@ react-outside-click-handler@1.3.0:
object.values "^1.1.0"
prop-types "^15.7.2"
-react-redux@7.2.6:
- version "7.2.6"
- resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz"
- integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
- dependencies:
- "@babel/runtime" "^7.15.4"
- "@types/react-redux" "^7.1.20"
- hoist-non-react-statics "^3.3.2"
- loose-envify "^1.4.0"
- prop-types "^15.7.2"
- react-is "^17.0.2"
-
react-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
@@ -10564,7 +10476,7 @@ readdirp@~3.5.0:
readdirp@~3.6.0:
version "3.6.0"
- resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
@@ -10584,38 +10496,6 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
-redux-devtools-extension@2.13.9:
- version "2.13.9"
- resolved "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz"
- integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==
-
-redux-mock-store@1.5.4:
- version "1.5.4"
- resolved "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz"
- integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==
- dependencies:
- lodash.isplainobject "^4.0.6"
-
-redux-thunk@2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
- integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
-
-redux@4.1.2:
- version "4.1.2"
- resolved "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz"
- integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
- dependencies:
- "@babel/runtime" "^7.9.2"
-
-redux@^4.0.0:
- version "4.0.5"
- resolved "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz"
- integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
- dependencies:
- loose-envify "^1.4.0"
- symbol-observable "^1.2.0"
-
redux@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz"
@@ -11411,21 +11291,16 @@ source-list-map@^2.0.0:
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-"source-map-js@>=0.6.2 <2.0.0":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
- integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
-source-map-js@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
- integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
-
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
version "0.5.3"
resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz"
@@ -11866,7 +11741,7 @@ swr@1.2.1:
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.1.tgz#c21a4fe2139cb1c4630450589b5b5add947a9d41"
integrity sha512-1cuWXqJqXcFwbgONGCY4PHZ8v05009JdHsC3CIC6u7d00kgbMswNr1sHnnhseOBxtzVqcCNpOHEgVDciRer45w==
-symbol-observable@1.2.0, symbol-observable@^1.2.0:
+symbol-observable@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
@@ -12123,13 +11998,6 @@ tough-cookie@^4.0.0:
punycode "^2.1.1"
universalify "^0.1.2"
-tr46@^1.0.1:
- version "1.0.1"
- resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz"
- integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
- dependencies:
- punycode "^2.1.0"
-
tr46@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz"
@@ -12137,11 +12005,6 @@ tr46@^2.0.2:
dependencies:
punycode "^2.1.1"
-tr46@~0.0.3:
- version "0.0.3"
- resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
- integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
-
tryer@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz"
@@ -12588,16 +12451,6 @@ web-vitals@2.1.4:
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
-webidl-conversions@^3.0.0:
- version "3.0.1"
- resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
- integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
-
-webidl-conversions@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz"
- integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz"
@@ -12780,23 +12633,6 @@ whatwg-mimetype@^2.3.0:
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
-whatwg-url@^5.0.0:
- version "5.0.0"
- resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
- integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
- dependencies:
- tr46 "~0.0.3"
- webidl-conversions "^3.0.0"
-
-whatwg-url@^6.5.0:
- version "6.5.0"
- resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz"
- integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
- dependencies:
- lodash.sortby "^4.7.0"
- tr46 "^1.0.1"
- webidl-conversions "^4.0.2"
-
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.5.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz"