about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorSorin Davidoi <sorin.davidoi@gmail.com>2017-06-20 20:40:03 +0200
committerEugen Rochko <eugen@zeonfederated.com>2017-06-20 20:40:03 +0200
commit8f03fdce7fa16fcaa829a40f395cefc85eb957d5 (patch)
tree8eba2f952ccfebc5abb892893a7c4ee97581c588 /app/javascript
parent1fc6cb499742c1a872ad717a689c58a80aeb714d (diff)
Upgrade React Router (#3677)
* chore(yarn): Remove react-router

* chore(yarn): Remove react-router-scroll

* chore(yarn): Remove history

* chore(yarn): Add react-router-dom

* chore: Remove usages of react-router-scroll

* refactor: Upgrade to react-router-web

* refactor: Use fork of react-router-scroll

This reverts commit 2ddea9a6c8d39fc64b7d0b587f3fbda7a45a7fa2.

* fix: Issues mentions in the PR feedback
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/components/column_back_button.js4
-rw-r--r--app/javascript/mastodon/components/column_back_button_slim.js4
-rw-r--r--app/javascript/mastodon/components/column_header.js4
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js2
-rw-r--r--app/javascript/mastodon/components/permalink.js2
-rw-r--r--app/javascript/mastodon/components/status.js4
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js8
-rw-r--r--app/javascript/mastodon/components/status_content.js4
-rw-r--r--app/javascript/mastodon/containers/mastodon.js70
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js2
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js4
-rw-r--r--app/javascript/mastodon/features/compose/components/navigation_bar.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/reply_indicator.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js2
-rw-r--r--app/javascript/mastodon/features/compose/index.js2
-rw-r--r--app/javascript/mastodon/features/follow_requests/index.js1
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js2
-rw-r--r--app/javascript/mastodon/features/home_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/report/index.js4
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js4
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js4
-rw-r--r--app/javascript/mastodon/features/status/index.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/boost_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/column_link.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/tabs_bar.js14
-rw-r--r--app/javascript/mastodon/features/ui/index.js95
26 files changed, 144 insertions, 104 deletions
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
index 3add65968..191c0f597 100644
--- a/app/javascript/mastodon/components/column_back_button.js
+++ b/app/javascript/mastodon/components/column_back_button.js
@@ -9,8 +9,8 @@ class ColumnBackButton extends React.PureComponent {
   };
 
   handleClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.push('/');
-    else this.context.router.goBack();
+    if (window.history && window.history.length === 1) this.context.router.history.push('/');
+    else this.context.router.history.goBack();
   }
 
   render () {
diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js
index 2d3f1b57a..ffb05fa02 100644
--- a/app/javascript/mastodon/components/column_back_button_slim.js
+++ b/app/javascript/mastodon/components/column_back_button_slim.js
@@ -9,8 +9,8 @@ class ColumnBackButtonSlim extends React.PureComponent {
   };
 
   handleClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.push('/');
-    else this.context.router.goBack();
+    if (window.history && window.history.length === 1) this.context.router.history.push('/');
+    else this.context.router.history.goBack();
   }
 
   render () {
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index 175ee800f..076f1cdad 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -45,8 +45,8 @@ class ColumnHeader extends React.PureComponent {
   }
 
   handleBackClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.push('/');
-    else this.context.router.goBack();
+    if (window.history && window.history.length === 1) this.context.router.history.push('/');
+    else this.context.router.history.goBack();
   }
 
   handleTransitionEnd = () => {
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 5712cffab..6e394a95d 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -41,7 +41,7 @@ class DropdownMenu extends React.PureComponent {
       action();
     } else if (to) {
       e.preventDefault();
-      this.context.router.push(to);
+      this.context.router.history.push(to);
     }
 
     this.dropdown.hide();
diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js
index b45969d85..7149e8380 100644
--- a/app/javascript/mastodon/components/permalink.js
+++ b/app/javascript/mastodon/components/permalink.js
@@ -17,7 +17,7 @@ class Permalink extends React.PureComponent {
   handleClick = (e) => {
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
-      this.context.router.push(this.props.to);
+      this.context.router.history.push(this.props.to);
     }
   }
 
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 8c4443967..b2214f72a 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -144,14 +144,14 @@ class Status extends ImmutablePureComponent {
 
   handleClick = () => {
     const { status } = this.props;
-    this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
+    this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
   }
 
   handleAccountClick = (e) => {
     if (e.button === 0) {
       const id = Number(e.currentTarget.getAttribute('data-id'));
       e.preventDefault();
-      this.context.router.push(`/accounts/${id}`);
+      this.context.router.history.push(`/accounts/${id}`);
     }
   }
 
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 99e01d972..800f0d0fb 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -53,7 +53,7 @@ class StatusActionBar extends ImmutablePureComponent {
   ]
 
   handleReplyClick = () => {
-    this.props.onReply(this.props.status, this.context.router);
+    this.props.onReply(this.props.status, this.context.router.history);
   }
 
   handleFavouriteClick = () => {
@@ -69,7 +69,7 @@ class StatusActionBar extends ImmutablePureComponent {
   }
 
   handleMentionClick = () => {
-    this.props.onMention(this.props.status.get('account'), this.context.router);
+    this.props.onMention(this.props.status.get('account'), this.context.router.history);
   }
 
   handleMuteClick = () => {
@@ -81,12 +81,12 @@ class StatusActionBar extends ImmutablePureComponent {
   }
 
   handleOpen = () => {
-    this.context.router.push(`/statuses/${this.props.status.get('id')}`);
+    this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
   }
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-    this.context.router.push('/report');
+    this.context.router.history.push('/report');
   }
 
   handleConversationMuteClick = () => {
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 0a71cbba7..d02083d3e 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -56,7 +56,7 @@ class StatusContent extends React.PureComponent {
   onMentionClick = (mention, e) => {
     if (e.button === 0) {
       e.preventDefault();
-      this.context.router.push(`/accounts/${mention.get('id')}`);
+      this.context.router.history.push(`/accounts/${mention.get('id')}`);
     }
   }
 
@@ -65,7 +65,7 @@ class StatusContent extends React.PureComponent {
 
     if (e.button === 0) {
       e.preventDefault();
-      this.context.router.push(`/timelines/tag/${hashtag}`);
+      this.context.router.history.push(`/timelines/tag/${hashtag}`);
     }
   }
 
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 5e009cfa3..d44cb1be4 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -12,35 +12,10 @@ import {
 } from '../actions/timelines';
 import { showOnboardingOnce } from '../actions/onboarding';
 import { updateNotifications, refreshNotifications } from '../actions/notifications';
-import createBrowserHistory from 'history/lib/createBrowserHistory';
-import applyRouterMiddleware from 'react-router/lib/applyRouterMiddleware';
-import useRouterHistory from 'react-router/lib/useRouterHistory';
-import Router from 'react-router/lib/Router';
-import Route from 'react-router/lib/Route';
-import IndexRedirect from 'react-router/lib/IndexRedirect';
-import IndexRoute from 'react-router/lib/IndexRoute';
-import { useScroll } from 'react-router-scroll';
+import BrowserRouter from 'react-router-dom/BrowserRouter';
+import Route from 'react-router-dom/Route';
+import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext';
 import UI from '../features/ui';
-import Status from '../features/status';
-import GettingStarted from '../features/getting_started';
-import PublicTimeline from '../features/public_timeline';
-import CommunityTimeline from '../features/community_timeline';
-import AccountTimeline from '../features/account_timeline';
-import AccountGallery from '../features/account_gallery';
-import HomeTimeline from '../features/home_timeline';
-import Compose from '../features/compose';
-import Followers from '../features/followers';
-import Following from '../features/following';
-import Reblogs from '../features/reblogs';
-import Favourites from '../features/favourites';
-import HashtagTimeline from '../features/hashtag_timeline';
-import Notifications from '../features/notifications';
-import FollowRequests from '../features/follow_requests';
-import GenericNotFound from '../features/generic_not_found';
-import FavouritedStatuses from '../features/favourited_statuses';
-import Blocks from '../features/blocks';
-import Mutes from '../features/mutes';
-import Report from '../features/report';
 import { hydrateStore } from '../actions/store';
 import createStream from '../stream';
 import { IntlProvider, addLocaleData } from 'react-intl';
@@ -52,10 +27,6 @@ const store = configureStore();
 const initialState = JSON.parse(document.getElementById('initial-state').textContent);
 store.dispatch(hydrateStore(initialState));
 
-const browserHistory = useRouterHistory(createBrowserHistory)({
-  basename: '/web',
-});
-
 class Mastodon extends React.PureComponent {
 
   componentDidMount() {
@@ -136,36 +107,11 @@ class Mastodon extends React.PureComponent {
     return (
       <IntlProvider locale={locale} messages={messages}>
         <Provider store={store}>
-          <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
-            <Route path='/' component={UI}>
-              <IndexRedirect to='/getting-started' />
-              <Route path='getting-started' component={GettingStarted} />
-              <Route path='timelines/home' component={HomeTimeline} />
-              <Route path='timelines/public' component={PublicTimeline} />
-              <Route path='timelines/public/local' component={CommunityTimeline} />
-              <Route path='timelines/tag/:id' component={HashtagTimeline} />
-
-              <Route path='notifications' component={Notifications} />
-              <Route path='favourites' component={FavouritedStatuses} />
-
-              <Route path='statuses/new' component={Compose} />
-              <Route path='statuses/:statusId' component={Status} />
-              <Route path='statuses/:statusId/reblogs' component={Reblogs} />
-              <Route path='statuses/:statusId/favourites' component={Favourites} />
-
-              <Route path='accounts/:accountId' component={AccountTimeline} />
-              <Route path='accounts/:accountId/followers' component={Followers} />
-              <Route path='accounts/:accountId/following' component={Following} />
-              <Route path='accounts/:accountId/media' component={AccountGallery} />
-
-              <Route path='follow_requests' component={FollowRequests} />
-              <Route path='blocks' component={Blocks} />
-              <Route path='mutes' component={Mutes} />
-              <Route path='report' component={Report} />
-
-              <Route path='*' component={GenericNotFound} />
-            </Route>
-          </Router>
+          <BrowserRouter basename='/web'>
+            <ScrollContext>
+              <Route path='/' component={UI} />
+            </ScrollContext>
+          </BrowserRouter>
         </Provider>
       </IntlProvider>
     );
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index 15fdd1a50..0ea8ad347 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -2,7 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import DropdownMenu from '../../../components/dropdown_menu';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
 
 const messages = defineMessages({
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index d2fe86476..55fdc4167 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -33,12 +33,12 @@ class Header extends ImmutablePureComponent {
   }
 
   handleMention = () => {
-    this.props.onMention(this.props.account, this.context.router);
+    this.props.onMention(this.props.account, this.context.router.history);
   }
 
   handleReport = () => {
     this.props.onReport(this.props.account);
-    this.context.router.push('/report');
+    this.context.router.history.push('/report');
   }
 
   handleMute = () => {
diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js
index 6f3dbc5af..1c135a733 100644
--- a/app/javascript/mastodon/features/compose/components/navigation_bar.js
+++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js
@@ -5,7 +5,7 @@ import IconButton from '../../../components/icon_button';
 import DisplayName from '../../../components/display_name';
 import Permalink from '../../../components/permalink';
 import { FormattedMessage } from 'react-intl';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 class NavigationBar extends ImmutablePureComponent {
diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js
index 8ad401121..474549a5f 100644
--- a/app/javascript/mastodon/features/compose/components/reply_indicator.js
+++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js
@@ -31,7 +31,7 @@ class ReplyIndicator extends ImmutablePureComponent {
   handleAccountClick = (e) => {
     if (e.button === 0) {
       e.preventDefault();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
   }
 
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index a553a8280..26d766a1c 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import AccountContainer from '../../../containers/account_container';
 import StatusContainer from '../../../containers/status_container';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 class SearchResults extends ImmutablePureComponent {
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index e7d933e86..a87e48a23 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -5,7 +5,7 @@ import NavigationContainer from './containers/navigation_container';
 import PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import { mountCompose, unmountCompose } from '../../actions/compose';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import { injectIntl, defineMessages } from 'react-intl';
 import SearchContainer from './containers/search_container';
 import Motion from 'react-motion/lib/Motion';
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index 446fdbc6e..286f128f4 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -54,6 +54,7 @@ class FollowRequests extends ImmutablePureComponent {
     return (
       <Column icon='users' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
+
         <ScrollContainer scrollKey='follow_requests'>
           <div className='scrollable' onScroll={this.handleScroll}>
             {accountIds.map(id =>
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index f30dea446..a4549e609 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -2,7 +2,7 @@ import React from 'react';
 import Column from '../ui/components/column';
 import ColumnLink from '../ui/components/column_link';
 import ColumnSubheading from '../ui/components/column_subheading';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index 6d3968751..ddebf2379 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -8,7 +8,7 @@ import ColumnHeader from '../../components/column_header';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 
 const messages = defineMessages({
   title: { id: 'column.home', defaultMessage: 'Home' },
diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js
index 217802b5c..23aba39de 100644
--- a/app/javascript/mastodon/features/report/index.js
+++ b/app/javascript/mastodon/features/report/index.js
@@ -52,7 +52,7 @@ class Report extends React.PureComponent {
 
   componentWillMount () {
     if (!this.props.account) {
-      this.context.router.replace('/');
+      this.context.router.history.replace('/');
     }
   }
 
@@ -76,7 +76,7 @@ class Report extends React.PureComponent {
 
   handleSubmit = () => {
     this.props.dispatch(submitReport());
-    this.context.router.replace('/');
+    this.context.router.history.replace('/');
   }
 
   render () {
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 16ea83e40..21c2fd682 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -50,12 +50,12 @@ class ActionBar extends React.PureComponent {
   }
 
   handleMentionClick = () => {
-    this.props.onMention(this.props.status.get('account'), this.context.router);
+    this.props.onMention(this.props.status.get('account'), this.context.router.history);
   }
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-    this.context.router.push('/report');
+    this.context.router.history.push('/report');
   }
 
   render () {
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 6bbb8ca33..a77c4f0bb 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -7,7 +7,7 @@ import StatusContent from '../../../components/status_content';
 import MediaGallery from '../../../components/media_gallery';
 import VideoPlayer from '../../../components/video_player';
 import AttachmentList from '../../../components/attachment_list';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 import { FormattedDate, FormattedNumber } from 'react-intl';
 import CardContainer from '../containers/card_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -28,7 +28,7 @@ class DetailedStatus extends ImmutablePureComponent {
   handleAccountClick = (e) => {
     if (e.button === 0) {
       e.preventDefault();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
 
     e.stopPropagation();
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index f8a87ccb1..19cee2435 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -93,7 +93,7 @@ class Status extends ImmutablePureComponent {
   }
 
   handleReplyClick = (status) => {
-    this.props.dispatch(replyCompose(status, this.context.router));
+    this.props.dispatch(replyCompose(status, this.context.router.history));
   }
 
   handleModalReblog = (status) => {
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index 9d99b5336..da2be5264 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.js
@@ -40,7 +40,7 @@ class BoostModal extends ImmutablePureComponent {
     if (e.button === 0) {
       e.preventDefault();
       this.props.onClose();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
     }
   }
 
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
index 24387af57..cbdb6534f 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.js
+++ b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Link from 'react-router/lib/Link';
+import Link from 'react-router-dom/Link';
 
 const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
   if (href) {
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index b108e8244..265cb818b 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -1,5 +1,5 @@
 import React from 'react';
-import Link from 'react-router/lib/Link';
+import NavLink from 'react-router-dom/NavLink';
 import { FormattedMessage } from 'react-intl';
 
 class TabsBar extends React.Component {
@@ -7,14 +7,14 @@ class TabsBar extends React.Component {
   render () {
     return (
       <div className='tabs-bar'>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link>
+        <NavLink className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>
+        <NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
+        <NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
 
-        <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link>
-        <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link>
+        <NavLink className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
+        <NavLink className='tabs-bar__link secondary' activeClassName='active' exact to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
 
-        <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
+        <NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></NavLink>
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 9f31b5443..39600607f 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -1,4 +1,7 @@
 import React from 'react';
+import Switch from 'react-router-dom/Switch';
+import Route from 'react-router-dom/Route';
+import Redirect from 'react-router-dom/Redirect';
 import NotificationsContainer from './containers/notifications_container';
 import PropTypes from 'prop-types';
 import LoadingBarContainer from './containers/loading_bar_container';
@@ -13,6 +16,67 @@ import { refreshNotifications } from '../../actions/notifications';
 import UploadArea from './components/upload_area';
 import ColumnsAreaContainer from './containers/columns_area_container';
 
+import Status from '../../features/status';
+import GettingStarted from '../../features/getting_started';
+import PublicTimeline from '../../features/public_timeline';
+import CommunityTimeline from '../../features/community_timeline';
+import AccountTimeline from '../../features/account_timeline';
+import AccountGallery from '../../features/account_gallery';
+import HomeTimeline from '../../features/home_timeline';
+import Compose from '../../features/compose';
+import Followers from '../../features/followers';
+import Following from '../../features/following';
+import Reblogs from '../../features/reblogs';
+import Favourites from '../../features/favourites';
+import HashtagTimeline from '../../features/hashtag_timeline';
+import Notifications from '../../features/notifications';
+import FollowRequests from '../../features/follow_requests';
+import GenericNotFound from '../../features/generic_not_found';
+import FavouritedStatuses from '../../features/favourited_statuses';
+import Blocks from '../../features/blocks';
+import Mutes from '../../features/mutes';
+import Report from '../../features/report';
+
+// Small wrapper to pass multiColumn to the route components
+const WrappedSwitch = ({ multiColumn, children }) => (
+  <Switch>
+    {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
+  </Switch>
+);
+
+WrappedSwitch.propTypes = {
+  multiColumn: PropTypes.bool,
+  children: PropTypes.node,
+};
+
+// Small Wraper to extract the params from the route and pass
+// them to the rendered component, together with the content to
+// be rendered inside (the children)
+class WrappedRoute extends React.Component {
+
+  static propTypes = {
+    component: PropTypes.func.isRequired,
+    content: PropTypes.node,
+    multiColumn: PropTypes.bool,
+  }
+
+  renderComponent = ({ match: { params } }) => {
+    const { component: Component, content, multiColumn } = this.props;
+
+    return <Component params={params} multiColumn={multiColumn}>{content}</Component>;
+  }
+
+  render () {
+    const { component: Component, content, ...rest } = this.props;
+
+    return <Route {...rest} render={this.renderComponent} />;
+  }
+
+}
+
+const noOp = () => false;
+
+
 class UI extends React.PureComponent {
 
   static propTypes = {
@@ -119,7 +183,36 @@ class UI extends React.PureComponent {
     return (
       <div className='ui' ref={this.setRef}>
         <TabsBar />
-        <ColumnsAreaContainer singleColumn={isMobile(width)}>{children}</ColumnsAreaContainer>
+        <ColumnsAreaContainer singleColumn={isMobile(width)}>
+          <WrappedSwitch>
+            <Redirect from='/' to='/getting-started' exact />
+            <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
+            <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
+            <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
+            <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
+            <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
+
+            <WrappedRoute path='/notifications' component={Notifications} content={children} />
+            <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
+
+            <WrappedRoute path='/statuses/new' component={Compose} content={children} />
+            <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
+            <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
+            <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
+
+            <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
+            <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
+            <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
+            <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
+
+            <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
+            <WrappedRoute path='/blocks' component={Blocks} content={children} />
+            <WrappedRoute path='/mutes' component={Mutes} content={children} />
+            <WrappedRoute path='/report' component={Report} content={children} />
+
+            <WrappedRoute component={GenericNotFound} content={children} />
+          </WrappedSwitch>
+        </ColumnsAreaContainer>
         <NotificationsContainer />
         <LoadingBarContainer className='loading-bar' />
         <ModalContainer />