diff --git a/src/common/MenuLayout.js b/src/common/MenuLayout.js index e009a5ce..0038e907 100644 --- a/src/common/MenuLayout.js +++ b/src/common/MenuLayout.js @@ -1,110 +1,85 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Route, Switch } from 'react-router-dom'; import { Swipeable } from 'react-swipeable'; import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import classnames from 'classnames'; +import classNames from 'classnames'; import * as PropTypes from 'prop-types'; import { serverType } from '../servers/prop-types'; +import MutedMessage from '../utils/MutedMessage'; import NotFound from './NotFound'; import './MenuLayout.scss'; -const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) => - class MenuLayout extends React.Component { - static propTypes = { - match: PropTypes.object, - selectServer: PropTypes.func, - location: PropTypes.object, - selectedServer: serverType, - }; +const propTypes = { + match: PropTypes.object, + selectServer: PropTypes.func, + location: PropTypes.object, + selectedServer: serverType, +}; - state = { showSideBar: false }; +const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) => { + const MenuLayoutComp = ({ match, location, selectedServer, selectServer }) => { + const [ showSideBar, setShowSidebar ] = useState(false); - componentDidMount() { - const { match, selectServer } = this.props; + useEffect(() => { const { params: { serverId } } = match; selectServer(serverId); + }, []); + useEffect(() => setShowSidebar(false), [ location ]); + + if (!selectedServer) { + return ; } - componentDidUpdate(prevProps) { - const { location } = this.props; - - // Hide sidebar when location changes - if (location !== prevProps.location) { - this.setState({ showSideBar: false }); + const { params: { serverId } } = match; + const burgerClasses = classNames('menu-layout__burger-icon', { + 'menu-layout__burger-icon--active': showSideBar, + }); + const swipeMenuIfNoModalExists = (showSideBar) => () => { + if (document.querySelector('.modal')) { + return; } - } - render() { - const { selectedServer, match } = this.props; - const { params: { serverId } } = match; - const burgerClasses = classnames('menu-layout__burger-icon', { - 'menu-layout__burger-icon--active': this.state.showSideBar, - }); - const swipeMenuIfNoModalExists = (showSideBar) => () => { - if (document.querySelector('.modal')) { - return; - } + setShowSidebar(showSideBar); + }; - this.setState({ showSideBar }); - }; + return ( + + setShowSidebar(!showSideBar)} + /> - return ( - - this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))} - /> - - -
- -
this.setState({ showSideBar: false })} - > - - - - - - } - /> - -
+ +
+ +
setShowSidebar(false)}> + + + + + + } + /> +
- - - ); - } +
+
+ + ); }; + MenuLayoutComp.propTypes = propTypes; + + return MenuLayoutComp; +}; + export default MenuLayout; diff --git a/src/servers/reducers/selectedServer.js b/src/servers/reducers/selectedServer.js index f683fbe7..29dfb4e9 100644 --- a/src/servers/reducers/selectedServer.js +++ b/src/servers/reducers/selectedServer.js @@ -19,7 +19,7 @@ export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serve dispatch(resetShortUrlParams()); const selectedServer = findServerById(serverId); - const { health } = await buildShlinkApiClient(selectedServer); + const { health } = buildShlinkApiClient(selectedServer); const version = await health() .then(({ version }) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version) .then((version) => !versionIsValidSemVer(version) ? MIN_FALLBACK_VERSION : version) diff --git a/src/short-urls/reducers/shortUrlCreation.js b/src/short-urls/reducers/shortUrlCreation.js index 0640f2c7..dc99e0ac 100644 --- a/src/short-urls/reducers/shortUrlCreation.js +++ b/src/short-urls/reducers/shortUrlCreation.js @@ -31,8 +31,7 @@ export default handleActions({ export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => { dispatch({ type: CREATE_SHORT_URL_START }); - - const { createShortUrl } = await buildShlinkApiClient(getState); + const { createShortUrl } = buildShlinkApiClient(getState); try { const result = await createShortUrl(data); diff --git a/src/short-urls/reducers/shortUrlDeletion.js b/src/short-urls/reducers/shortUrlDeletion.js index 62c78569..a878d07f 100644 --- a/src/short-urls/reducers/shortUrlDeletion.js +++ b/src/short-urls/reducers/shortUrlDeletion.js @@ -32,8 +32,7 @@ export default handleActions({ export const deleteShortUrl = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => { dispatch({ type: DELETE_SHORT_URL_START }); - - const { deleteShortUrl } = await buildShlinkApiClient(getState); + const { deleteShortUrl } = buildShlinkApiClient(getState); try { await deleteShortUrl(shortCode, domain); diff --git a/src/short-urls/reducers/shortUrlMeta.js b/src/short-urls/reducers/shortUrlMeta.js index c05bf4ba..7582414f 100644 --- a/src/short-urls/reducers/shortUrlMeta.js +++ b/src/short-urls/reducers/shortUrlMeta.js @@ -37,7 +37,7 @@ export default handleActions({ export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, domain, meta) => async (dispatch, getState) => { dispatch({ type: EDIT_SHORT_URL_META_START }); - const { updateShortUrlMeta } = await buildShlinkApiClient(getState); + const { updateShortUrlMeta } = buildShlinkApiClient(getState); try { await updateShortUrlMeta(shortCode, domain, meta); diff --git a/src/short-urls/reducers/shortUrlTags.js b/src/short-urls/reducers/shortUrlTags.js index c48e2e03..46c91d54 100644 --- a/src/short-urls/reducers/shortUrlTags.js +++ b/src/short-urls/reducers/shortUrlTags.js @@ -31,7 +31,7 @@ export default handleActions({ export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, tags) => async (dispatch, getState) => { dispatch({ type: EDIT_SHORT_URL_TAGS_START }); - const { updateShortUrlTags } = await buildShlinkApiClient(getState); + const { updateShortUrlTags } = buildShlinkApiClient(getState); try { const normalizedTags = await updateShortUrlTags(shortCode, domain, tags); diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 02efcfd7..efd8978b 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -58,8 +58,7 @@ export default handleActions({ export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => { dispatch({ type: LIST_SHORT_URLS_START }); - - const { listShortUrls } = await buildShlinkApiClient(getState); + const { listShortUrls } = buildShlinkApiClient(getState); try { const shortUrls = await listShortUrls(params); diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js index 1982f6fc..e9964c22 100644 --- a/src/tags/TagsList.js +++ b/src/tags/TagsList.js @@ -1,7 +1,7 @@ import React from 'react'; import { splitEvery } from 'ramda'; import PropTypes from 'prop-types'; -import MuttedMessage from '../utils/MuttedMessage'; +import MutedMessage from '../utils/MutedMessage'; import SearchField from '../utils/SearchField'; const { ceil } = Math; @@ -29,7 +29,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component { const { tagsList, match } = this.props; if (tagsList.loading) { - return Loading...; + return ; } if (tagsList.error) { @@ -43,7 +43,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component { const tagsCount = tagsList.filteredTags.length; if (tagsCount < 1) { - return No tags found; + return No tags found; } const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags); diff --git a/src/tags/reducers/tagDelete.js b/src/tags/reducers/tagDelete.js index 0420da6d..14486bea 100644 --- a/src/tags/reducers/tagDelete.js +++ b/src/tags/reducers/tagDelete.js @@ -26,8 +26,7 @@ export default handleActions({ export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => { dispatch({ type: DELETE_TAG_START }); - - const { deleteTags } = await buildShlinkApiClient(getState); + const { deleteTags } = buildShlinkApiClient(getState); try { await deleteTags([ tag ]); diff --git a/src/tags/reducers/tagEdit.js b/src/tags/reducers/tagEdit.js index 560397a8..095d87a0 100644 --- a/src/tags/reducers/tagEdit.js +++ b/src/tags/reducers/tagEdit.js @@ -31,8 +31,7 @@ export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newNa getState ) => { dispatch({ type: EDIT_TAG_START }); - - const { editTag } = await buildShlinkApiClient(getState); + const { editTag } = buildShlinkApiClient(getState); try { await editTag(oldName, newName); diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index 684a64d3..6cb2ff49 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -50,7 +50,7 @@ export const listTags = (buildShlinkApiClient, force = true) => () => async (dis dispatch({ type: LIST_TAGS_START }); try { - const { listTags } = await buildShlinkApiClient(getState); + const { listTags } = buildShlinkApiClient(getState); const tags = await listTags(); dispatch({ tags, type: LIST_TAGS }); diff --git a/src/utils/MutedMessage.js b/src/utils/MutedMessage.js new file mode 100644 index 00000000..61e5c90c --- /dev/null +++ b/src/utils/MutedMessage.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { Card } from 'reactstrap'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +const propTypes = { + noMargin: PropTypes.bool, + loading: PropTypes.bool, + children: PropTypes.node, +}; + +const MutedMessage = ({ children, loading = false, noMargin = false }) => { + const cardClasses = classNames('bg-light', { + 'mt-4': !noMargin, + }); + + return ( +
+ +

+ {loading && } + {loading && !children && Loading...} + {children} +

+
+
+ ); +}; + +MutedMessage.propTypes = propTypes; + +export default MutedMessage; diff --git a/src/utils/MuttedMessage.js b/src/utils/MuttedMessage.js deleted file mode 100644 index 3b2e6cc6..00000000 --- a/src/utils/MuttedMessage.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { Card } from 'reactstrap'; -import classnames from 'classnames'; -import PropTypes from 'prop-types'; - -const DEFAULT_MARGIN_SIZE = 4; -const propTypes = { - marginSize: PropTypes.number, - children: PropTypes.node, -}; - -export default function MutedMessage({ children, marginSize = DEFAULT_MARGIN_SIZE }) { - const cardClasses = classnames('bg-light', { - [`mt-${marginSize}`]: marginSize > 0, - }); - - return ( -
- -

- {children} -

-
-
- ); -} - -MutedMessage.propTypes = propTypes; diff --git a/src/utils/services/ShlinkApiClientBuilder.js b/src/utils/services/ShlinkApiClientBuilder.js index be170789..a436f698 100644 --- a/src/utils/services/ShlinkApiClientBuilder.js +++ b/src/utils/services/ShlinkApiClientBuilder.js @@ -1,21 +1,16 @@ -import { wait } from '../utils'; import ShlinkApiClient from './ShlinkApiClient'; const apiClients = {}; -const getSelectedServerFromState = async (getState) => { +const getSelectedServerFromState = (getState) => { const { selectedServer } = getState(); - if (!selectedServer) { - return wait(250).then(() => getSelectedServerFromState(getState)); - } - return selectedServer; }; -const buildShlinkApiClient = (axios) => async (getStateOrSelectedServer) => { +const buildShlinkApiClient = (axios) => (getStateOrSelectedServer) => { const { url, apiKey } = typeof getStateOrSelectedServer === 'function' - ? await getSelectedServerFromState(getStateOrSelectedServer) + ? getSelectedServerFromState(getStateOrSelectedServer) : getStateOrSelectedServer; const clientKey = `${url}_${apiKey}`; diff --git a/src/utils/utils.js b/src/utils/utils.js index 97595ddc..48af3f55 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -53,8 +53,6 @@ export const useToggle = (initialValue = false) => { return [ flag, () => setFlag(!flag) ]; }; -export const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); - export const compareVersions = (firstVersion, operator, secondVersion) => compare( firstVersion, secondVersion, diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index 7b1ae484..e235ffef 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -1,12 +1,10 @@ -import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { isEmpty, mapObjIndexed, values } from 'ramda'; import React from 'react'; import { Card } from 'reactstrap'; import PropTypes from 'prop-types'; import qs from 'qs'; import DateRangeRow from '../utils/DateRangeRow'; -import MutedMessage from '../utils/MuttedMessage'; +import MutedMessage from '../utils/MutedMessage'; import { formatDate } from '../utils/utils'; import SortableBarGraph from './SortableBarGraph'; import { shortUrlVisitsType } from './reducers/shortUrlVisits'; @@ -66,7 +64,7 @@ const ShortUrlVisits = ( if (loading) { const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...'; - return {message}; + return {message}; } if (error) { diff --git a/src/visits/reducers/shortUrlDetail.js b/src/visits/reducers/shortUrlDetail.js index 2f22243c..477464f5 100644 --- a/src/visits/reducers/shortUrlDetail.js +++ b/src/visits/reducers/shortUrlDetail.js @@ -28,8 +28,7 @@ export default handleActions({ export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => { dispatch({ type: GET_SHORT_URL_DETAIL_START }); - - const { getShortUrl } = await buildShlinkApiClient(getState); + const { getShortUrl } = buildShlinkApiClient(getState); try { const shortUrl = await getShortUrl(shortCode, domain); diff --git a/src/visits/reducers/shortUrlVisits.js b/src/visits/reducers/shortUrlVisits.js index 8b23125f..ac6b2dd5 100644 --- a/src/visits/reducers/shortUrlVisits.js +++ b/src/visits/reducers/shortUrlVisits.js @@ -51,8 +51,7 @@ export default handleActions({ export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query) => async (dispatch, getState) => { dispatch({ type: GET_SHORT_URL_VISITS_START }); - - const { getShortUrlVisits } = await buildShlinkApiClient(getState); + const { getShortUrlVisits } = buildShlinkApiClient(getState); const itemsPerPage = 5000; const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount; diff --git a/test/servers/reducers/selectedServer.test.js b/test/servers/reducers/selectedServer.test.js index f7650f69..4d20fb5c 100644 --- a/test/servers/reducers/selectedServer.test.js +++ b/test/servers/reducers/selectedServer.test.js @@ -38,7 +38,7 @@ describe('selectedServerReducer', () => { const apiClientMock = { health: jest.fn(), }; - const buildApiClient = jest.fn().mockResolvedValue(apiClientMock); + const buildApiClient = jest.fn().mockReturnValue(apiClientMock); const dispatch = jest.fn(); afterEach(jest.clearAllMocks); diff --git a/test/short-urls/reducers/shortUrlMeta.test.js b/test/short-urls/reducers/shortUrlMeta.test.js index 8775d22d..a02b385a 100644 --- a/test/short-urls/reducers/shortUrlMeta.test.js +++ b/test/short-urls/reducers/shortUrlMeta.test.js @@ -51,7 +51,7 @@ describe('shortUrlMetaReducer', () => { describe('editShortUrlMeta', () => { const updateShortUrlMeta = jest.fn().mockResolvedValue({}); - const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlMeta }); + const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlMeta }); const dispatch = jest.fn(); afterEach(jest.clearAllMocks); diff --git a/test/short-urls/reducers/shortUrlTags.test.js b/test/short-urls/reducers/shortUrlTags.test.js index 967953c8..98bd13a7 100644 --- a/test/short-urls/reducers/shortUrlTags.test.js +++ b/test/short-urls/reducers/shortUrlTags.test.js @@ -51,14 +51,10 @@ describe('shortUrlTagsReducer', () => { describe('editShortUrlTags', () => { const updateShortUrlTags = jest.fn(); - const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags }); + const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlTags }); const dispatch = jest.fn(); - afterEach(() => { - updateShortUrlTags.mockReset(); - buildShlinkApiClient.mockClear(); - dispatch.mockReset(); - }); + afterEach(jest.clearAllMocks); it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches normalized tags on success', async (domain) => { const normalizedTags = [ 'bar', 'foo' ]; diff --git a/test/tags/TagsList.test.js b/test/tags/TagsList.test.js index 60c9b234..5a2ce549 100644 --- a/test/tags/TagsList.test.js +++ b/test/tags/TagsList.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { identity } from 'ramda'; import createTagsList from '../../src/tags/TagsList'; -import MuttedMessage from '../../src/utils/MuttedMessage'; +import MutedMessage from '../../src/utils/MutedMessage'; import SearchField from '../../src/utils/SearchField'; import { rangeOf } from '../../src/utils/utils'; @@ -28,7 +28,7 @@ describe('', () => { it('shows a loading message when tags are being loaded', () => { const wrapper = createWrapper({ loading: true }); - const loadingMsg = wrapper.find(MuttedMessage); + const loadingMsg = wrapper.find(MutedMessage); expect(loadingMsg).toHaveLength(1); expect(loadingMsg.html()).toContain('Loading...'); @@ -44,7 +44,7 @@ describe('', () => { it('shows a message when the list of tags is empty', () => { const wrapper = createWrapper({ filteredTags: [] }); - const msg = wrapper.find(MuttedMessage); + const msg = wrapper.find(MutedMessage); expect(msg).toHaveLength(1); expect(msg.html()).toContain('No tags found'); diff --git a/test/tags/reducers/tagsList.test.js b/test/tags/reducers/tagsList.test.js index cd64c151..7cede265 100644 --- a/test/tags/reducers/tagsList.test.js +++ b/test/tags/reducers/tagsList.test.js @@ -104,7 +104,7 @@ describe('tagsListReducer', () => { const tags = [ 'foo', 'bar', 'baz' ]; listTagsMock.mockResolvedValue(tags); - buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock }); + buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock }); await listTags(buildShlinkApiClient, true)()(dispatch, getState); @@ -127,7 +127,7 @@ describe('tagsListReducer', () => { it('dispatches error when error occurs on list call', async () => { listTagsMock.mockRejectedValue(new Error()); - buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock }); + buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock }); await assertErrorResult(); @@ -135,7 +135,9 @@ describe('tagsListReducer', () => { }); it('dispatches error when error occurs on build call', async () => { - buildShlinkApiClient.mockRejectedValue(new Error()); + buildShlinkApiClient.mockImplementation(() => { + throw new Error(); + }); await assertErrorResult(); diff --git a/test/utils/services/ShlinkApiClientBuilder.test.js b/test/utils/services/ShlinkApiClientBuilder.test.js index 74ee7df6..2490ca27 100644 --- a/test/utils/services/ShlinkApiClientBuilder.test.js +++ b/test/utils/services/ShlinkApiClientBuilder.test.js @@ -34,10 +34,10 @@ describe('ShlinkApiClientBuilder', () => { expect(secondApiClient).toBe(thirdApiClient); }); - it('does not fetch from state when provided param is already selected server', async () => { + it('does not fetch from state when provided param is already selected server', () => { const url = 'url'; const apiKey = 'apiKey'; - const apiClient = await buildShlinkApiClient({})({ url, apiKey }); + const apiClient = buildShlinkApiClient({})({ url, apiKey }); expect(apiClient._baseUrl).toEqual(url); expect(apiClient._apiKey).toEqual(apiKey); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index b136efde..004019cf 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -3,7 +3,7 @@ import { shallow } from 'enzyme'; import { identity } from 'ramda'; import { Card } from 'reactstrap'; import createShortUrlVisits from '../../src/visits/ShortUrlVisits'; -import MutedMessage from '../../src/utils/MuttedMessage'; +import MutedMessage from '../../src/utils/MutedMessage'; import GraphCard from '../../src/visits/GraphCard'; import SortableBarGraph from '../../src/visits/SortableBarGraph'; import DateRangeRow from '../../src/utils/DateRangeRow';