mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Merge pull request #257 from acelaya-forks/feature/navigate-back
Feature/navigate back
This commit is contained in:
commit
3953e98a77
7 changed files with 103 additions and 80 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue