Merge pull request #257 from acelaya-forks/feature/navigate-back

Feature/navigate back
This commit is contained in:
Alejandro Celaya 2020-04-26 12:02:54 +02:00 committed by GitHub
commit 3953e98a77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 80 deletions

View file

@ -19,7 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
#### Changed #### Changed
* *Nothing* * [#218](https://github.com/shlinkio/shlink-web-client/issues/218) Added back button to sections not displayed in left menu
#### Deprecated #### Deprecated

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button } from 'reactstrap';
import NoMenuLayout from '../common/NoMenuLayout'; import NoMenuLayout from '../common/NoMenuLayout';
import { ServerForm } from './helpers/ServerForm'; import { ServerForm } from './helpers/ServerForm';
import { withSelectedServer } from './helpers/withSelectedServer'; import { withSelectedServer } from './helpers/withSelectedServer';
@ -10,11 +11,12 @@ const propTypes = {
selectedServer: serverType, selectedServer: serverType,
history: PropTypes.shape({ history: PropTypes.shape({
push: PropTypes.func, push: PropTypes.func,
goBack: PropTypes.func,
}), }),
}; };
export const EditServer = (ServerError) => { export const EditServer = (ServerError) => {
const EditServerComp = ({ editServer, selectedServer, history: { push } }) => { const EditServerComp = ({ editServer, selectedServer, history: { push, goBack } }) => {
const handleSubmit = (serverData) => { const handleSubmit = (serverData) => {
editServer(selectedServer.id, serverData); editServer(selectedServer.id, serverData);
push(`/server/${selectedServer.id}/list-short-urls/1`); push(`/server/${selectedServer.id}/list-short-urls/1`);
@ -23,7 +25,8 @@ export const EditServer = (ServerError) => {
return ( return (
<NoMenuLayout> <NoMenuLayout>
<ServerForm initialValues={selectedServer} onSubmit={handleSubmit}> <ServerForm initialValues={selectedServer} onSubmit={handleSubmit}>
<button className="btn btn-outline-primary">Save</button> <Button outline className="mr-2" onClick={goBack}>Cancel</Button>
<Button outline color="primary">Save</Button>
</ServerForm> </ServerForm>
</NoMenuLayout> </NoMenuLayout>
); );

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect } from 'react';
import { splitEvery } from 'ramda'; import { splitEvery } from 'ramda';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Message from '../utils/Message'; import Message from '../utils/Message';
@ -7,8 +7,7 @@ import SearchField from '../utils/SearchField';
const { ceil } = Math; const { ceil } = Math;
const TAGS_GROUPS_AMOUNT = 4; const TAGS_GROUPS_AMOUNT = 4;
const TagsList = (TagCard) => class TagsList extends React.Component { const propTypes = {
static propTypes = {
filterTags: PropTypes.func, filterTags: PropTypes.func,
forceListTags: PropTypes.func, forceListTags: PropTypes.func,
tagsList: PropTypes.shape({ tagsList: PropTypes.shape({
@ -17,17 +16,15 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
filteredTags: PropTypes.arrayOf(PropTypes.string), filteredTags: PropTypes.arrayOf(PropTypes.string),
}), }),
match: PropTypes.object, match: PropTypes.object,
}; };
componentDidMount() {
const { forceListTags } = this.props;
const TagsList = (TagCard) => {
const TagListComp = ({ filterTags, forceListTags, tagsList, match }) => {
useEffect(() => {
forceListTags(); forceListTags();
} }, []);
renderContent() {
const { tagsList, match } = this.props;
const renderContent = () => {
if (tagsList.loading) { if (tagsList.loading) {
return <Message noMargin loading />; return <Message noMargin loading />;
} }
@ -63,22 +60,21 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
))} ))}
</React.Fragment> </React.Fragment>
); );
} };
render() {
const { filterTags } = this.props;
return ( return (
<React.Fragment> <React.Fragment>
{!this.props.tagsList.loading && {!tagsList.loading && <SearchField className="mb-3" placeholder="Search tags..." onChange={filterTags} />}
<SearchField className="mb-3" placeholder="Search tags..." onChange={filterTags} />
}
<div className="row"> <div className="row">
{this.renderContent()} {renderContent()}
</div> </div>
</React.Fragment> </React.Fragment>
); );
} };
TagListComp.propTypes = propTypes;
return TagListComp;
}; };
export default TagsList; export default TagsList;

View file

@ -21,6 +21,9 @@ import { shortUrlDetailType } from './reducers/shortUrlDetail';
import VisitsTable from './VisitsTable'; import VisitsTable from './VisitsTable';
const propTypes = { const propTypes = {
history: PropTypes.shape({
goBack: PropTypes.func,
}),
match: PropTypes.shape({ match: PropTypes.shape({
params: PropTypes.object, params: PropTypes.object,
}), }),
@ -53,6 +56,7 @@ let selectedBar;
const ShortUrlVisits = ({ processStatsFromVisits, normalizeVisits }, OpenMapModalBtn) => { const ShortUrlVisits = ({ processStatsFromVisits, normalizeVisits }, OpenMapModalBtn) => {
const ShortUrlVisitsComp = ({ const ShortUrlVisitsComp = ({
history,
match, match,
location, location,
shortUrlVisits, shortUrlVisits,
@ -204,7 +208,7 @@ const ShortUrlVisits = ({ processStatsFromVisits, normalizeVisits }, OpenMapModa
return ( return (
<React.Fragment> <React.Fragment>
<VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} /> <VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={history.goBack} />
<section className="mt-4"> <section className="mt-4">
<div className="row flex-md-row-reverse"> <div className="row flex-md-row-reverse">

View file

@ -1,7 +1,10 @@
import { Card, UncontrolledTooltip } from 'reactstrap'; import { Button, Card, UncontrolledTooltip } from 'reactstrap';
import Moment from 'react-moment'; import Moment from 'react-moment';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount'; import ShortUrlVisitsCount from '../short-urls/helpers/ShortUrlVisitsCount';
import { shortUrlDetailType } from './reducers/shortUrlDetail'; import { shortUrlDetailType } from './reducers/shortUrlDetail';
import { shortUrlVisitsType } from './reducers/shortUrlVisits'; import { shortUrlVisitsType } from './reducers/shortUrlVisits';
@ -10,9 +13,10 @@ import './VisitsHeader.scss';
const propTypes = { const propTypes = {
shortUrlDetail: shortUrlDetailType.isRequired, shortUrlDetail: shortUrlDetailType.isRequired,
shortUrlVisits: shortUrlVisitsType.isRequired, shortUrlVisits: shortUrlVisitsType.isRequired,
goBack: PropTypes.func.isRequired,
}; };
export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) { export default function VisitsHeader({ shortUrlDetail, shortUrlVisits, goBack }) {
const { shortUrl, loading } = shortUrlDetail; const { shortUrl, loading } = shortUrlDetail;
const { visits } = shortUrlVisits; const { visits } = shortUrlVisits;
const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : ''; const shortLink = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : '';
@ -26,17 +30,28 @@ export default function VisitsHeader({ shortUrlDetail, shortUrlVisits }) {
</UncontrolledTooltip> </UncontrolledTooltip>
</span> </span>
); );
const visitsStatsTitle = (
<React.Fragment>
Visit stats for <ExternalLink href={shortLink} />
</React.Fragment>
);
return ( return (
<header> <header>
<Card className="bg-light" body> <Card className="bg-light" body>
<h2> <h2 className="d-flex justify-content-between align-items-center">
<span className="badge badge-main float-right"> <Button color="link" size="lg" className="p-0 mr-3" onClick={goBack}>
<FontAwesomeIcon icon={faArrowLeft} />
</Button>
<span className="text-center d-none d-sm-block">
{visitsStatsTitle}
</span>
<span className="badge badge-main ml-3">
Visits:{' '} Visits:{' '}
<ShortUrlVisitsCount visitsCount={visits.length} shortUrl={shortUrl} /> <ShortUrlVisitsCount visitsCount={visits.length} shortUrl={shortUrl} />
</span> </span>
Visit stats for <ExternalLink href={shortLink} />
</h2> </h2>
<h3 className="text-center d-block d-sm-none mb-0">{visitsStatsTitle}</h3>
<hr /> <hr />
<div>Created: {renderDate()}</div> <div>Created: {renderDate()}</div>
<div> <div>

View file

@ -18,6 +18,9 @@ describe('<ShortUrlVisits />', () => {
params: { shortCode: 'abc123' }, params: { shortCode: 'abc123' },
}; };
const location = { search: '' }; const location = { search: '' };
const history = {
goBack: jest.fn(),
};
const createComponent = (shortUrlVisits) => { const createComponent = (shortUrlVisits) => {
const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits, normalizeVisits: identity }, () => ''); const ShortUrlVisits = createShortUrlVisits({ processStatsFromVisits, normalizeVisits: identity }, () => '');
@ -28,6 +31,7 @@ describe('<ShortUrlVisits />', () => {
getShortUrlVisits={getShortUrlVisitsMock} getShortUrlVisits={getShortUrlVisitsMock}
match={match} match={match}
location={location} location={location}
history={history}
shortUrlVisits={shortUrlVisits} shortUrlVisits={shortUrlVisits}
shortUrlDetail={{}} shortUrlDetail={{}}
cancelGetShortUrlVisits={identity} cancelGetShortUrlVisits={identity}

View file

@ -17,9 +17,10 @@ describe('<VisitsHeader />', () => {
const shortUrlVisits = { const shortUrlVisits = {
visits: [{}, {}, {}], visits: [{}, {}, {}],
}; };
const goBack = jest.fn();
beforeEach(() => { beforeEach(() => {
wrapper = shallow(<VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} />); wrapper = shallow(<VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />);
}); });
afterEach(() => wrapper.unmount()); afterEach(() => wrapper.unmount());