Update everything to work with the new router
By the way, remove the router state from redux.
This commit is contained in:
parent
2897f73cb9
commit
c5cafacbf1
@ -51,6 +51,15 @@ export function getUserInfos() {
|
||||
)
|
||||
}
|
||||
|
||||
export function setUserToken(token) {
|
||||
return {
|
||||
type: "USER_SET_TOKEN",
|
||||
payload: {
|
||||
token: token,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserTokens() {
|
||||
return request(
|
||||
"GET_USER_TOKENS",
|
||||
|
@ -2,14 +2,14 @@
|
||||
import "file-loader?name=[name].[ext]!../index.html"
|
||||
|
||||
// Import default image
|
||||
import "file-loader?name=img/[name].png!../img/noimage.png"
|
||||
import "file-loader?name=img/[name].[ext]!../img/noimage.png"
|
||||
|
||||
// Import favicon settings
|
||||
import "file-loader?name=[name].png!../img/apple-touch-icon.png"
|
||||
import "file-loader?name=[name].png!../img/favicon-16x16.png"
|
||||
import "file-loader?name=[name].png!../img/favicon-32x32.png"
|
||||
import "file-loader?name=[name].png!../img/favicon.ico"
|
||||
import "file-loader?name=[name].png!../img/safari-pinned-tab.svg"
|
||||
import "file-loader?name=[name].[ext]!../img/apple-touch-icon.png"
|
||||
import "file-loader?name=[name].[ext]!../img/favicon-16x16.png"
|
||||
import "file-loader?name=[name].[ext]!../img/favicon-32x32.png"
|
||||
import "file-loader?name=[name].[ext]!../img/favicon.ico"
|
||||
import "file-loader?name=[name].[ext]!../img/safari-pinned-tab.svg"
|
||||
|
||||
// Styles
|
||||
import "../less/app.less"
|
||||
@ -17,65 +17,72 @@ import "../less/app.less"
|
||||
// React
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { Provider, connect } from "react-redux"
|
||||
import { Router } from "react-router"
|
||||
import { Provider } from "react-redux"
|
||||
import { Router, Route, Switch, Redirect } from "react-router-dom"
|
||||
|
||||
// Action creators
|
||||
import { dismissAlert } from "./actions/alerts"
|
||||
// Auth
|
||||
import { ProtectedRoute, AdminRoute } from "./auth"
|
||||
|
||||
// Store
|
||||
import store, { history } from "./store"
|
||||
|
||||
// Components
|
||||
import NavBar from "./components/navbar"
|
||||
import AdminPanel from "./components/admins/panel"
|
||||
import Alert from "./components/alerts/alert"
|
||||
import MovieList from "./components/movies/list"
|
||||
import MoviesRoute from "./components/movies/route"
|
||||
import NavBar from "./components/navbar"
|
||||
import ShowDetails from "./components/shows/details"
|
||||
import ShowList from "./components/shows/list"
|
||||
import ShowsRoute from "./components/shows/route"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
import TorrentSearch from "./components/torrents/search"
|
||||
import UserActivation from "./components/users/activation"
|
||||
import UserLoginForm from "./components/users/login"
|
||||
import UserLogout from "./components/users/logout"
|
||||
import UserProfile from "./components/users/profile"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import UserTokens from "./components/users/tokens"
|
||||
|
||||
// Routes
|
||||
import getRoutes from "./routes"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
let torrentCount = 0;
|
||||
if (state.torrentStore.has("torrents") && state.torrentStore.get("torrents") !== undefined) {
|
||||
torrentCount = state.torrentStore.get("torrents").size;
|
||||
}
|
||||
return {
|
||||
username: state.userStore.get("username"),
|
||||
isAdmin: state.userStore.get("isAdmin"),
|
||||
isActivated: state.userStore.get("isActivated"),
|
||||
torrentCount: torrentCount,
|
||||
alerts: state.alerts,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators({ dismissAlert }, dispatch);
|
||||
}
|
||||
|
||||
function Main(props) {
|
||||
return (
|
||||
const App = () => (
|
||||
<div>
|
||||
<NavBar
|
||||
username={props.username}
|
||||
isAdmin={props.isAdmin}
|
||||
isActivated={props.isActivated}
|
||||
router={props.router}
|
||||
torrentCount={props.torrentCount}
|
||||
/>
|
||||
<Alert
|
||||
alerts={props.alerts}
|
||||
dismissAlert={props.dismissAlert}
|
||||
/>
|
||||
<NavBar />
|
||||
<Alert />
|
||||
<div className="container-fluid">
|
||||
{props.children}
|
||||
<Switch>
|
||||
<AdminRoute path="/admin" exact component={AdminPanel} />
|
||||
<Route path="/users/profile" exact component={UserProfile} />
|
||||
<Route path="/users/tokens" exact component={UserTokens} />
|
||||
<Route path="/torrents/list" exact component={TorrentList} />
|
||||
<Route path="/torrents/search" exact component={TorrentSearch} />
|
||||
<Route path="/torrents/search/:type/:search" exact component={TorrentSearch} />
|
||||
<MoviesRoute path="/movies/polochon" exact component={MovieList} />
|
||||
<MoviesRoute path="/movies/wishlist" exact component={MovieList} />
|
||||
<MoviesRoute path="/movies/search/:search" exact component={MovieList} />
|
||||
<MoviesRoute path="/movies/explore/:source/:category" exact component={MovieList} />
|
||||
<ShowsRoute path="/shows/polochon" exact component={ShowList} />
|
||||
<ShowsRoute path="/shows/wishlist" exact component={ShowList} />
|
||||
<ShowsRoute path="/shows/search/:search" exact component={ShowList} />
|
||||
<ShowsRoute path="/shows/explore/:source/:category" exact component={ShowList} />
|
||||
<ShowsRoute path="/shows/details/:imdbId" exact component={ShowDetails} />
|
||||
<Route render={() =>
|
||||
<Redirect to="/movies/explore/yts/seeds" />
|
||||
}/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const App = connect(mapStateToProps, mapDispatchToProps)(Main);
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history} routes={getRoutes(App)} />
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/users/login" exact component={UserLoginForm} />
|
||||
<Route path="/users/logout" exact component={UserLogout} />
|
||||
<Route path="/users/signup" exact component={UserSignUp} />
|
||||
<Route path="/users/activation" exact component={UserActivation} />
|
||||
<ProtectedRoute path="*" component={App} />
|
||||
</Switch>
|
||||
</Router>
|
||||
</Provider>
|
||||
),document.getElementById("app"));
|
||||
|
80
frontend/js/auth.js
Normal file
80
frontend/js/auth.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { Route, Redirect } from "react-router-dom"
|
||||
import { setUserToken } from "./actions/users"
|
||||
|
||||
const protectedRoute = ({
|
||||
component: Component,
|
||||
isLogged,
|
||||
isActivated,
|
||||
isTokenSet,
|
||||
setUserToken,
|
||||
...otherProps,
|
||||
}) => {
|
||||
const isAuthenticated = () => {
|
||||
if (isTokenSet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
if (isLogged || (token && token !== "")) {
|
||||
if (!isTokenSet) {
|
||||
setUserToken(token);
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
<Route {...otherProps} render={(props) => {
|
||||
if (isAuthenticated()) {
|
||||
if (isActivated) {
|
||||
return <Component {...props} />
|
||||
} else {
|
||||
return <Redirect to="/users/activation" />
|
||||
}
|
||||
} else {
|
||||
return <Redirect to="/users/login" />
|
||||
}
|
||||
}} />
|
||||
)
|
||||
}
|
||||
protectedRoute.propTypes = {
|
||||
component: PropTypes.func,
|
||||
isLogged: PropTypes.bool.isRequired,
|
||||
isActivated: PropTypes.bool.isRequired,
|
||||
isTokenSet: PropTypes.bool.isRequired,
|
||||
setUserToken: PropTypes.func.isRequired,
|
||||
};
|
||||
export const ProtectedRoute = connect((state) => ({
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
isAdmin: state.userStore.get("isLogged"),
|
||||
isActivated: state.userStore.get("isActivated"),
|
||||
isTokenSet: state.userStore.get("isTokenSet"),
|
||||
}), { setUserToken })(protectedRoute);
|
||||
|
||||
const adminRoute = ({
|
||||
component: Component,
|
||||
isAdmin,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<Route {...otherProps} render={(props) => {
|
||||
if (isAdmin) {
|
||||
return <Component {...props} />
|
||||
} else {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
}} />
|
||||
)
|
||||
}
|
||||
adminRoute.propTypes = {
|
||||
component: PropTypes.func,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
};
|
||||
export const AdminRoute = connect((state) => ({
|
||||
isAdmin: state.userStore.get("isLogged"),
|
||||
}))(adminRoute);
|
@ -1,25 +1,40 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { updateUser } from "../../actions/admins"
|
||||
import {
|
||||
getUsers, getStats,
|
||||
getAdminModules, updateUser
|
||||
} from "../../actions/admins"
|
||||
|
||||
import Modules from "../modules/modules"
|
||||
import { UserList } from "./users"
|
||||
import { Stats } from "./stats"
|
||||
|
||||
const AdminPanel = props => (
|
||||
const AdminPanel = props => {
|
||||
const [fetched, setIsFetched] = useState(false);
|
||||
if (!fetched) {
|
||||
props.getUsers();
|
||||
props.getStats();
|
||||
props.getAdminModules();
|
||||
setIsFetched(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stats stats={props.stats}/>
|
||||
<UserList users={props.users} updateUser={props.updateUser}/>
|
||||
<Modules modules={props.modules}/>
|
||||
<Modules modules={props.modules} isLoading={false} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
AdminPanel.propTypes = {
|
||||
stats: PropTypes.object,
|
||||
users: PropTypes.object,
|
||||
modules: PropTypes.object,
|
||||
updateUser: PropTypes.func,
|
||||
updateUser: PropTypes.func.isRequired,
|
||||
getUsers: PropTypes.func.isRequired,
|
||||
getStats: PropTypes.func.isRequired,
|
||||
getAdminModules: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@ -27,7 +42,9 @@ const mapStateToProps = state => ({
|
||||
stats: state.adminStore.get("stats"),
|
||||
modules: state.adminStore.get("modules"),
|
||||
});
|
||||
const mapDispatchToProps = dipatch =>
|
||||
bindActionCreators({ updateUser }, dipatch)
|
||||
const mapDispatchToProps = {
|
||||
getUsers, getStats,
|
||||
getAdminModules, updateUser
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminPanel);
|
||||
|
@ -1,16 +1,35 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import SweetAlert from "react-bootstrap-sweetalert";
|
||||
import { connect } from "react-redux"
|
||||
|
||||
export default function Alert(props) {
|
||||
if (!props.alerts.get("show")) {
|
||||
import { dismissAlert } from "../../actions/alerts"
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
show: state.alerts.get("show"),
|
||||
title: state.alerts.get("message"),
|
||||
type: state.alerts.get("type"),
|
||||
});
|
||||
const mapDispatchToProps = { dismissAlert };
|
||||
|
||||
const Alert = (props) => {
|
||||
if (!props.show) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SweetAlert
|
||||
type={props.alerts.get("type")}
|
||||
title={props.alerts.get("message")}
|
||||
type={props.type}
|
||||
title={props.title}
|
||||
onConfirm={props.dismissAlert}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Alert.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
dismissAlert: PropTypes.func.isRequired,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Alert);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import { Map, List } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default function ListDetails(props) {
|
||||
return (
|
||||
const ListDetails = (props) => (
|
||||
<div className="col-xs-7 col-md-4">
|
||||
<div className="affix">
|
||||
<h1 className="hidden-xs">{props.data.get("title")}</h1>
|
||||
@ -29,10 +30,14 @@ export default function ListDetails(props) {
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
ListDetails.propTypes = {
|
||||
data: PropTypes.instanceOf(Map).isRequired,
|
||||
children: PropTypes.object,
|
||||
};
|
||||
export default ListDetails;
|
||||
|
||||
function Runtime(props) {
|
||||
const Runtime = (props) => {
|
||||
if (props.runtime === undefined) {
|
||||
return null;
|
||||
}
|
||||
@ -44,8 +49,9 @@ function Runtime(props) {
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Runtime.propTypes = { runtime: PropTypes.number };
|
||||
|
||||
function Ratings(props) {
|
||||
const Ratings = (props) => {
|
||||
if (props.rating === undefined) {
|
||||
return null;
|
||||
}
|
||||
@ -60,8 +66,12 @@ function Ratings(props) {
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Ratings.propTypes = {
|
||||
rating: PropTypes.number,
|
||||
votes: PropTypes.number,
|
||||
};
|
||||
|
||||
function TrackingLabel(props) {
|
||||
const TrackingLabel = (props) => {
|
||||
let wishlistStr = props.wishlisted ? "Wishlisted" : "";
|
||||
|
||||
if (props.trackedEpisode !== null && props.trackedSeason !== null
|
||||
@ -83,8 +93,13 @@ function TrackingLabel(props) {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
TrackingLabel.propTypes = {
|
||||
wishlisted: PropTypes.bool,
|
||||
trackedSeason: PropTypes.number,
|
||||
trackedEpisode: PropTypes.number,
|
||||
};
|
||||
|
||||
function PolochonMetadata(props) {
|
||||
const PolochonMetadata = (props) => {
|
||||
if (!props.quality || props.quality === "") {
|
||||
return null;
|
||||
}
|
||||
@ -99,8 +114,15 @@ function PolochonMetadata(props) {
|
||||
</p>
|
||||
);
|
||||
}
|
||||
PolochonMetadata.propTypes = {
|
||||
quality: PropTypes.string,
|
||||
container: PropTypes.string,
|
||||
videoCodec: PropTypes.string,
|
||||
audioCodec: PropTypes.string,
|
||||
releaseGroup: PropTypes.string,
|
||||
};
|
||||
|
||||
function Genres(props) {
|
||||
const Genres = (props) => {
|
||||
if (props.genres === undefined) {
|
||||
return null;
|
||||
}
|
||||
@ -117,3 +139,6 @@ function Genres(props) {
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Genres.propTypes = {
|
||||
genres: PropTypes.instanceOf(List),
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import { withRouter } from "react-router-dom"
|
||||
import { Form, FormGroup, FormControl, ControlLabel } from "react-bootstrap"
|
||||
|
||||
export default class ExplorerOptions extends React.PureComponent {
|
||||
class ExplorerOptions extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSourceChange = this.handleSourceChange.bind(this);
|
||||
@ -10,12 +11,12 @@ export default class ExplorerOptions extends React.PureComponent {
|
||||
handleSourceChange(event) {
|
||||
let source = event.target.value;
|
||||
let category = this.props.options.get(event.target.value).first();
|
||||
this.props.router.push(`/${this.props.type}/explore/${source}/${category}`);
|
||||
this.props.history.push(`/${this.props.type}/explore/${source}/${category}`);
|
||||
}
|
||||
handleCategoryChange(event) {
|
||||
let source = this.props.params.source;
|
||||
let category = event.target.value;
|
||||
this.props.router.push(`/${this.props.type}/explore/${source}/${category}`);
|
||||
this.props.history.push(`/${this.props.type}/explore/${source}/${category}`);
|
||||
}
|
||||
propsValid(props) {
|
||||
if (!props.params
|
||||
@ -96,3 +97,5 @@ export default class ExplorerOptions extends React.PureComponent {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(ExplorerOptions);
|
||||
|
@ -95,7 +95,6 @@ export default class ListPosters extends React.PureComponent {
|
||||
type={this.props.type}
|
||||
display={displayExplorerOptions}
|
||||
params={this.props.params}
|
||||
router={this.props.router}
|
||||
options={this.props.exploreOptions}
|
||||
/>
|
||||
<Posters
|
||||
@ -216,3 +215,4 @@ class Posters extends React.PureComponent {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from "react"
|
||||
import Loading from "react-loading"
|
||||
|
||||
export default function Loader() {
|
||||
return (
|
||||
const Loader = () => (
|
||||
<div className="row" id="container">
|
||||
<div className="col-md-6 col-md-offset-3">
|
||||
<Loading
|
||||
@ -13,5 +12,5 @@ export default function Loader() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
export default Loader;
|
||||
|
@ -1,30 +1,38 @@
|
||||
import React from "react"
|
||||
import Loader from "../loader/loader"
|
||||
import PropTypes from "prop-types"
|
||||
import { Map, List } from "immutable"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
export default function Modules(props) {
|
||||
const Modules = (props) => {
|
||||
if (props.isLoading) {
|
||||
return <Loader />
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Modules</h2>
|
||||
{props.modules && props.modules.keySeq().map(function(value, key) {
|
||||
return (
|
||||
{props.modules && props.modules.keySeq().map((value, key) => (
|
||||
<ModulesByVideoType
|
||||
key={value}
|
||||
key={key}
|
||||
videoType={value}
|
||||
data={props.modules.get(value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Modules.propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
modules: PropTypes.instanceOf(Map),
|
||||
};
|
||||
export default Modules;
|
||||
|
||||
function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
const capitalize = (string) =>
|
||||
string.charAt(0).toUpperCase() + string.slice(1);
|
||||
|
||||
function ModulesByVideoType(props) {
|
||||
return (
|
||||
const ModulesByVideoType = (props) => (
|
||||
<div className="col-md-6 col-xs-12">
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">
|
||||
@ -33,23 +41,23 @@ function ModulesByVideoType(props) {
|
||||
</h3>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{props.data.keySeq().map(function(value, key) {
|
||||
return (
|
||||
{props.data.keySeq().map((value, key) => (
|
||||
<ModuleByType
|
||||
key={value}
|
||||
key={key}
|
||||
type={value}
|
||||
data={props.data.get(value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ModulesByVideoType.propTypes = {
|
||||
videoType: PropTypes.string.isRequired,
|
||||
data: PropTypes.instanceOf(Map),
|
||||
};
|
||||
|
||||
function ModuleByType(props) {
|
||||
return (
|
||||
const ModuleByType = (props) => (
|
||||
<div className="col-md-3 col-xs-6">
|
||||
<h4>{props.type} {/* Detailer / Explorer / ... */}</h4>
|
||||
<table className="table">
|
||||
@ -67,10 +75,14 @@ function ModuleByType(props) {
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ModuleByType.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
data: PropTypes.instanceOf(List),
|
||||
};
|
||||
|
||||
function Module(props) {
|
||||
var iconClass, prettyStatus, labelClass
|
||||
const Module = (props) => {
|
||||
let iconClass, prettyStatus, labelClass;
|
||||
const name = props.data.get("name");
|
||||
|
||||
switch(props.data.get("status")) {
|
||||
case "ok":
|
||||
@ -95,14 +107,15 @@ function Module(props) {
|
||||
}
|
||||
|
||||
const tooltip = (
|
||||
<Tooltip id={`tooltip-status-${props.name}`}>
|
||||
<Tooltip id={`tooltip-status-${name}`}>
|
||||
<p><span className={labelClass}>Status: {prettyStatus}</span></p>
|
||||
<p>Error: {props.data.get("error")}</p>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<th>{props.data.get("name")}</th>
|
||||
<th>{name}</th>
|
||||
<td>
|
||||
<OverlayTrigger placement="right" overlay={tooltip}>
|
||||
<span className={labelClass}>
|
||||
@ -113,3 +126,6 @@ function Module(props) {
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
Module.propTypes = {
|
||||
data: PropTypes.instanceOf(Map),
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent } from "../../actions/torrents"
|
||||
import { refreshSubtitles } from "../../actions/subtitles"
|
||||
import { addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||
@ -24,10 +23,11 @@ function mapStateToProps(state) {
|
||||
exploreOptions : state.movieStore.get("exploreOptions"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dipatch) =>
|
||||
bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
|
||||
const mapDispatchToProps = {
|
||||
selectMovie, getMovieDetails, addTorrent,
|
||||
addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||
refreshSubtitles, updateFilter }, dipatch)
|
||||
refreshSubtitles, updateFilter,
|
||||
};
|
||||
|
||||
function MovieButtons(props) {
|
||||
const hasMovie = (props.movie.get("polochon_url") !== "");
|
||||
@ -94,8 +94,7 @@ class MovieList extends React.PureComponent {
|
||||
onClick={this.props.selectMovie}
|
||||
onDoubleClick={function() { return; }}
|
||||
onKeyEnter={function() { return; }}
|
||||
params={this.props.params}
|
||||
router={this.props.router}
|
||||
params={this.props.match.params}
|
||||
loading={this.props.loading}
|
||||
/>
|
||||
{selectedMovie !== undefined &&
|
||||
|
60
frontend/js/components/movies/route.js
Normal file
60
frontend/js/components/movies/route.js
Normal file
@ -0,0 +1,60 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { Route } from "react-router-dom"
|
||||
import { connect } from "react-redux"
|
||||
import { fetchMovies, getMovieExploreOptions } from "../../actions/movies"
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isExplorerFetched: (state.movieStore.get("exploreOptions").size !== 0)
|
||||
});
|
||||
const mapDispatchToProps = { fetchMovies, getMovieExploreOptions };
|
||||
|
||||
const MoviesRoute = ({
|
||||
component: Component,
|
||||
isExplorerFetched,
|
||||
fetchMovies,
|
||||
getMovieExploreOptions,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<Route {...otherProps} render={(props) => {
|
||||
let fetchUrl = "";
|
||||
switch (props.match.path) {
|
||||
case "/movies/polochon":
|
||||
fetchUrl = "/movies/polochon";
|
||||
break;
|
||||
case "/movies/wishlist":
|
||||
fetchUrl = "/wishlist/movies";
|
||||
break;
|
||||
case "/movies/search/:search":
|
||||
fetchUrl = "/movies/search/" + props.match.params.search;
|
||||
break;
|
||||
case "/movies/explore/:source/:category":
|
||||
if (!isExplorerFetched) {
|
||||
getMovieExploreOptions();
|
||||
}
|
||||
fetchUrl = "/movies/explore?source=" +
|
||||
encodeURI(props.match.params.source) +
|
||||
"&category=" + encodeURI(props.match.params.category);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (fetchUrl != "") {
|
||||
fetchMovies(fetchUrl);
|
||||
}
|
||||
|
||||
return <Component {...props} />
|
||||
}} />
|
||||
)
|
||||
}
|
||||
MoviesRoute.propTypes = {
|
||||
component: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
isExplorerFetched: PropTypes.bool.isRequired,
|
||||
fetchMovies: PropTypes.func.isRequired,
|
||||
getMovieExploreOptions: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MoviesRoute);
|
@ -1,126 +1,99 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import { Route, Link } from "react-router-dom"
|
||||
import { connect } from "react-redux"
|
||||
import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from "react-bootstrap"
|
||||
import { LinkContainer } from "react-router-bootstrap"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class AppNavBar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userLoggedIn: (props.username !== ""),
|
||||
displayMoviesSearch: this.shouldDisplayMoviesSearch(props.router),
|
||||
displayShowsSearch: this.shouldDisplayShowsSearch(props.router),
|
||||
expanded: false,
|
||||
const mapStateToProps = (state) => {
|
||||
let torrentCount = 0;
|
||||
if (state.torrentStore.has("torrents") && state.torrentStore.get("torrents") !== undefined) {
|
||||
torrentCount = state.torrentStore.get("torrents").size;
|
||||
}
|
||||
return {
|
||||
username: state.userStore.get("username"),
|
||||
isAdmin: state.userStore.get("isAdmin"),
|
||||
torrentCount: torrentCount,
|
||||
}
|
||||
};
|
||||
this.setExpanded = this.setExpanded.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update the state based on the next props
|
||||
const shouldDisplayMoviesSearch = this.shouldDisplayMoviesSearch(nextProps.router);
|
||||
const shouldDisplayShowsSearch = this.shouldDisplayShowsSearch(nextProps.router);
|
||||
if ((this.state.displayMoviesSearch !== shouldDisplayMoviesSearch)
|
||||
|| (this.state.displayShowsSearch !== shouldDisplayShowsSearch)) {
|
||||
this.setState({
|
||||
userLoggedIn: (nextProps.username !== ""),
|
||||
displayMoviesSearch: shouldDisplayMoviesSearch,
|
||||
displayShowsSearch: shouldDisplayShowsSearch,
|
||||
});
|
||||
}
|
||||
}
|
||||
shouldDisplayMoviesSearch(router) {
|
||||
return this.matchPath(router, "movies");
|
||||
}
|
||||
shouldDisplayShowsSearch(router) {
|
||||
return this.matchPath(router, "shows");
|
||||
}
|
||||
matchPath(router, keyword) {
|
||||
const location = router.getCurrentLocation().pathname;
|
||||
return (location.indexOf(keyword) !== -1)
|
||||
}
|
||||
setExpanded(value) {
|
||||
if (this.state.expanded === value) { return }
|
||||
this.setState({ expanded: value });
|
||||
}
|
||||
render() {
|
||||
const loggedAndActivated = (this.state.userLoggedIn && this.props.isActivated);
|
||||
const displayShowsSearch = (this.state.displayShowsSearch && loggedAndActivated);
|
||||
const displayMoviesSearch = (this.state.displayMoviesSearch && loggedAndActivated);
|
||||
|
||||
const AppNavBar = (props) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar fluid fixedTop collapseOnSelect expanded={this.state.expanded} onToggle={this.setExpanded}>
|
||||
<Navbar
|
||||
fluid fixedTop collapseOnSelect
|
||||
expanded={expanded} onToggle={() => setExpanded(!expanded)}>
|
||||
<Navbar.Header>
|
||||
<LinkContainer to="/">
|
||||
<Navbar.Brand><a href="#">Canapé</a></Navbar.Brand>
|
||||
<Navbar.Brand><Link to="/">Canapé</Link></Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
{loggedAndActivated &&
|
||||
<MoviesDropdown />
|
||||
}
|
||||
{loggedAndActivated &&
|
||||
<ShowsDropdown />
|
||||
}
|
||||
{loggedAndActivated &&
|
||||
<WishlistDropdown />
|
||||
}
|
||||
{loggedAndActivated &&
|
||||
<TorrentsDropdown torrentsCount={this.props.torrentCount} />
|
||||
}
|
||||
<TorrentsDropdown torrentsCount={props.torrentCount} />
|
||||
<UserDropdown
|
||||
username={this.props.username}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isActivated={this.props.isActivated}
|
||||
username={props.username}
|
||||
isAdmin={props.isAdmin}
|
||||
/>
|
||||
{displayMoviesSearch &&
|
||||
<Route path="/movies" render={(props) =>
|
||||
<Search
|
||||
placeholder="Search movies"
|
||||
router={this.props.router}
|
||||
path='/movies/search'
|
||||
setExpanded={this.setExpanded}
|
||||
history={props.history}
|
||||
/>
|
||||
}
|
||||
{displayShowsSearch &&
|
||||
}/>
|
||||
<Route path="/shows" render={(props) =>
|
||||
<Search
|
||||
placeholder="Search shows"
|
||||
router={this.props.router}
|
||||
path='/shows/search'
|
||||
setExpanded={this.setExpanded}
|
||||
history={props.history}
|
||||
/>
|
||||
}
|
||||
}/>
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
AppNavBar.propTypes = {
|
||||
torrentCount: PropTypes.number.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(AppNavBar);
|
||||
|
||||
const Search = (props) => {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const handleSearch = (ev) => {
|
||||
ev.preventDefault();
|
||||
props.history.push(`${props.path}/${encodeURI(search)}`);
|
||||
}
|
||||
|
||||
class Search extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSearch = this.handleSearch.bind(this);
|
||||
}
|
||||
handleSearch(ev) {
|
||||
ev.preventDefault();
|
||||
this.props.setExpanded(false);
|
||||
this.props.router.push(`${this.props.path}/${encodeURI(this.input.value)}`);
|
||||
}
|
||||
render() {
|
||||
return(
|
||||
<div className="navbar-form navbar-right">
|
||||
<form className="input-group" onSubmit={(ev) => this.handleSearch(ev)}>
|
||||
<form className="input-group" onSubmit={(ev) => handleSearch(ev)}>
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder={this.props.placeholder}
|
||||
ref={(input) => this.input = input}
|
||||
placeholder={props.placeholder}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Search.propTypes = {
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
||||
function MoviesDropdown() {
|
||||
return(
|
||||
const MoviesDropdown = () => (
|
||||
<Nav>
|
||||
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
||||
<LinkContainer to="/movies/explore/yts/seeds">
|
||||
@ -131,11 +104,9 @@ function MoviesDropdown() {
|
||||
</LinkContainer>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
function ShowsDropdown() {
|
||||
return(
|
||||
const ShowsDropdown = () => (
|
||||
<Nav>
|
||||
<NavDropdown title="Shows" id="navbar-shows-dropdown">
|
||||
<LinkContainer to="/shows/explore/eztv/rating">
|
||||
@ -147,10 +118,8 @@ function ShowsDropdown() {
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
||||
function UserDropdown(props) {
|
||||
if (props.username !== "") {
|
||||
return (
|
||||
<Nav pullRight>
|
||||
<NavDropdown title={props.username} id="navbar-dropdown-right">
|
||||
@ -159,38 +128,25 @@ function UserDropdown(props) {
|
||||
<MenuItem>Admin Panel</MenuItem>
|
||||
</LinkContainer>
|
||||
}
|
||||
{props.isActivated &&
|
||||
<LinkContainer to="/users/profile">
|
||||
<MenuItem>Profile</MenuItem>
|
||||
</LinkContainer>
|
||||
}
|
||||
{props.isActivated &&
|
||||
<LinkContainer to="/users/tokens">
|
||||
<MenuItem>Tokens</MenuItem>
|
||||
</LinkContainer>
|
||||
}
|
||||
<LinkContainer to="/users/logout">
|
||||
<MenuItem>Logout</MenuItem>
|
||||
</LinkContainer>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
} else {
|
||||
return(
|
||||
<Nav pullRight>
|
||||
<LinkContainer to="/users/signup">
|
||||
<NavItem>Sign up</NavItem>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/users/login">
|
||||
<NavItem>Login</NavItem>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
UserDropdown.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
function WishlistDropdown() {
|
||||
return(
|
||||
const WishlistDropdown = () => (
|
||||
<Nav>
|
||||
<NavDropdown title="Wishlist" id="navbar-wishlit-dropdown">
|
||||
<LinkContainer to="/movies/wishlist">
|
||||
@ -202,9 +158,8 @@ function WishlistDropdown() {
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentsDropdown(props) {
|
||||
const TorrentsDropdown = (props) => {
|
||||
const title = (<TorrentsDropdownTitle torrentsCount={props.torrentsCount} />)
|
||||
return(
|
||||
<Nav>
|
||||
@ -219,16 +174,21 @@ function TorrentsDropdown(props) {
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
TorrentsDropdown.propTypes = { torrentsCount: PropTypes.number.isRequired };
|
||||
|
||||
function TorrentsDropdownTitle(props) {
|
||||
const TorrentsDropdownTitle = (props) => {
|
||||
if (props.torrentsCount === 0) {
|
||||
return (
|
||||
<span>
|
||||
Torrents
|
||||
{props.torrentsCount > 0 &&
|
||||
<span>Torrents</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span> Torrents
|
||||
<span>
|
||||
<span className="label label-info">{props.torrentsCount}</span>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
TorrentsDropdownTitle.propTypes = { torrentsCount: PropTypes.number.isRequired };
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent } from "../../actions/torrents"
|
||||
import { refreshSubtitles } from "../../actions/subtitles"
|
||||
import { addShowToWishlist, deleteShowFromWishlist, getEpisodeDetails, fetchShowDetails } from "../../actions/shows"
|
||||
@ -20,10 +19,10 @@ function mapStateToProps(state) {
|
||||
show: state.showStore.get("show"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({addTorrent, addShowToWishlist, deleteShowFromWishlist,
|
||||
fetchShowDetails, getEpisodeDetails,
|
||||
refreshSubtitles }, dispatch)
|
||||
const mapDispatchToProps = {
|
||||
addTorrent, addShowToWishlist, deleteShowFromWishlist,
|
||||
fetchShowDetails, getEpisodeDetails, refreshSubtitles,
|
||||
};
|
||||
|
||||
class ShowDetails extends React.Component {
|
||||
render() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { selectShow, addShowToWishlist,
|
||||
deleteShowFromWishlist, getShowDetails, updateFilter } from "../../actions/shows"
|
||||
|
||||
@ -18,9 +17,10 @@ function mapStateToProps(state) {
|
||||
exploreOptions : state.showsStore.get("exploreOptions"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ selectShow, addShowToWishlist,
|
||||
deleteShowFromWishlist, getShowDetails, updateFilter }, dispatch)
|
||||
const mapDispatchToProps = {
|
||||
selectShow, addShowToWishlist, deleteShowFromWishlist,
|
||||
getShowDetails, updateFilter,
|
||||
};
|
||||
|
||||
class ShowList extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@ -29,7 +29,7 @@ class ShowList extends React.PureComponent {
|
||||
}
|
||||
|
||||
showDetails(imdbId) {
|
||||
return this.props.router.push("/shows/details/" + imdbId);
|
||||
return this.props.history.push("/shows/details/" + imdbId);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -51,8 +51,7 @@ class ShowList extends React.PureComponent {
|
||||
onClick={this.props.selectShow}
|
||||
onDoubleClick={this.showDetails}
|
||||
onKeyEnter={this.showDetails}
|
||||
router={this.props.router}
|
||||
params={this.props.params}
|
||||
params={this.props.match.params}
|
||||
loading={this.props.loading}
|
||||
/>
|
||||
{selectedShow &&
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
|
||||
import { Link } from "react-router"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DropdownButton } from "react-bootstrap"
|
||||
|
||||
import { WishlistButton, RefreshButton } from "../buttons/actions"
|
||||
|
63
frontend/js/components/shows/route.js
Normal file
63
frontend/js/components/shows/route.js
Normal file
@ -0,0 +1,63 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { Route } from "react-router-dom"
|
||||
import { connect } from "react-redux"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "../../actions/shows"
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isExplorerFetched: (state.showsStore.get("exploreOptions").size !== 0)
|
||||
});
|
||||
const mapDispatchToProps = { fetchShows, fetchShowDetails, getShowExploreOptions };
|
||||
|
||||
const ShowsRoute = ({
|
||||
component: Component,
|
||||
isExplorerFetched,
|
||||
fetchShows,
|
||||
fetchShowDetails,
|
||||
getShowExploreOptions,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<Route {...otherProps} render={(props) => {
|
||||
let fetchUrl = "";
|
||||
switch (props.match.path) {
|
||||
case "/shows/polochon":
|
||||
fetchUrl = "/shows/polochon";
|
||||
break;
|
||||
case "/shows/wishlist":
|
||||
fetchUrl = "/wishlist/shows";
|
||||
break;
|
||||
case "/shows/search/:search":
|
||||
fetchUrl = "/shows/search/" + props.match.params.search;
|
||||
break;
|
||||
case "/shows/explore/:source/:category":
|
||||
if (!isExplorerFetched) {
|
||||
getShowExploreOptions();
|
||||
}
|
||||
fetchUrl = "/shows/explore?source=" +
|
||||
encodeURI(props.match.params.source) +
|
||||
"&category=" + encodeURI(props.match.params.category);
|
||||
break;
|
||||
case "/shows/details/:imdbId":
|
||||
fetchShowDetails(props.match.params.imdbId);
|
||||
fetchUrl = ""
|
||||
}
|
||||
|
||||
if (fetchUrl != "") {
|
||||
fetchShows(fetchUrl);
|
||||
}
|
||||
|
||||
return <Component {...props} />
|
||||
}} />
|
||||
)
|
||||
}
|
||||
ShowsRoute.propTypes = {
|
||||
component: PropTypes.func,
|
||||
match: PropTypes.object,
|
||||
isExplorerFetched: PropTypes.bool.isRequired,
|
||||
fetchShows: PropTypes.func.isRequired,
|
||||
fetchShowDetails: PropTypes.func.isRequired,
|
||||
getShowExploreOptions: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowsRoute);
|
@ -1,66 +1,67 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { Map, List } from "immutable"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent, removeTorrent } from "../../actions/torrents"
|
||||
import { fetchTorrents, addTorrent, removeTorrent } from "../../actions/torrents"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { torrents: state.torrentStore.get("torrents") };
|
||||
const mapStateToProps = (state) => ({
|
||||
torrents: state.torrentStore.get("torrents")
|
||||
});
|
||||
const mapDispatchToProps = {
|
||||
fetchTorrents, addTorrent, removeTorrent,
|
||||
};
|
||||
|
||||
// TODO: fecth every x seconds
|
||||
const TorrentList = (props) => {
|
||||
const [fetched, setIsFetched] = useState(false);
|
||||
if (!fetched) {
|
||||
props.fetchTorrents();
|
||||
setIsFetched(true);
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent, removeTorrent }, dispatch)
|
||||
|
||||
class TorrentList extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<AddTorrent func={this.props.addTorrent} />
|
||||
<List
|
||||
torrents={this.props.torrents}
|
||||
removeTorrent={this.props.removeTorrent}
|
||||
<AddTorrent addTorrent={props.addTorrent} />
|
||||
<Torrents
|
||||
torrents={props.torrents}
|
||||
removeTorrent={props.removeTorrent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
TorrentList.propTypes = {
|
||||
fetchTorrents: PropTypes.func.isRequired,
|
||||
addTorrent: PropTypes.func.isRequired,
|
||||
removeTorrent: PropTypes.func.isRequired,
|
||||
torrents: PropTypes.instanceOf(List),
|
||||
};
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentList);
|
||||
|
||||
class AddTorrent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { url: "" };
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({ url: event.target.value });
|
||||
}
|
||||
handleSubmit(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
const AddTorrent = (props) => {
|
||||
const [url, setUrl] = useState("");
|
||||
|
||||
const handleSubmit = (ev) => {
|
||||
if (ev) { ev.preventDefault(); }
|
||||
if (url === "") { return; }
|
||||
props.addTorrent(url);
|
||||
setUrl("");
|
||||
}
|
||||
|
||||
if (this.state.url === "") {
|
||||
return;
|
||||
}
|
||||
this.setState({ url: "" });
|
||||
this.props.func(this.state.url);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
<form className="input-group" onSubmit={this.handleSubmit}>
|
||||
<form className="input-group" onSubmit={() => handleSubmit()}>
|
||||
<input
|
||||
className="form-control"
|
||||
placeholder="Add torrent URL"
|
||||
onChange={this.handleChange}
|
||||
value={this.state.url}
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<span className="input-group-btn">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={this.handleSubmit}
|
||||
onClick={() => handleSubmit()}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
@ -70,11 +71,12 @@ class AddTorrent extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
AddTorrent.propTypes = {
|
||||
addTorrent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class List extends React.PureComponent {
|
||||
render() {
|
||||
if (this.props.torrents.size === 0) {
|
||||
const Torrents = (props) => {
|
||||
if (props.torrents.size === 0) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
@ -86,54 +88,52 @@ class List extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
<h3>Torrents</h3>
|
||||
{this.props.torrents.map(function(el, index) {
|
||||
return (
|
||||
{props.torrents.map((el, index) => (
|
||||
<Torrent
|
||||
key={index}
|
||||
data={el}
|
||||
removeTorrent={this.props.removeTorrent}
|
||||
removeTorrent={props.removeTorrent}
|
||||
/>
|
||||
);
|
||||
}, this)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Torrents.propTypes = {
|
||||
removeTorrent: PropTypes.func.isRequired,
|
||||
torrents: PropTypes.instanceOf(List),
|
||||
};
|
||||
|
||||
const Torrent = (props) => {
|
||||
const handleClick = () => {
|
||||
props.removeTorrent(props.data.get("id"));
|
||||
}
|
||||
|
||||
class Torrent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick() {
|
||||
this.props.removeTorrent(this.props.data.get("id"));
|
||||
}
|
||||
render() {
|
||||
const done = this.props.data.get("is_finished");
|
||||
const done = props.data.get("is_finished");
|
||||
var progressStyle = "progress-bar progress-bar-info active";
|
||||
if (done) {
|
||||
progressStyle = "progress-bar progress-bar-success";
|
||||
}
|
||||
var percentDone = this.props.data.get("percent_done");
|
||||
var percentDone = props.data.get("percent_done");
|
||||
const started = (percentDone !== 0);
|
||||
if (started) {
|
||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||
}
|
||||
|
||||
// Pretty sizes
|
||||
const downloadedSize = prettySize(this.props.data.get("downloaded_size"));
|
||||
const totalSize = prettySize(this.props.data.get("total_size"));
|
||||
const downloadRate = prettySize(this.props.data.get("download_rate")) + "/s";
|
||||
const downloadedSize = prettySize(props.data.get("downloaded_size"));
|
||||
const totalSize = prettySize(props.data.get("total_size"));
|
||||
const downloadRate = prettySize(props.data.get("download_rate")) + "/s";
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">
|
||||
{this.props.data.get("name")}
|
||||
<span className="fa fa-trash clickable pull-right" onClick={this.handleClick}></span>
|
||||
{props.data.get("name")}
|
||||
<span className="fa fa-trash clickable pull-right" onClick={() => handleClick()}></span>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{started &&
|
||||
@ -156,9 +156,12 @@ class Torrent extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Torrent.propTypes = {
|
||||
removeTorrent: PropTypes.func.isRequired,
|
||||
data: PropTypes.instanceOf(Map),
|
||||
};
|
||||
|
||||
function prettySize(fileSizeInBytes) {
|
||||
const prettySize = (fileSizeInBytes) => {
|
||||
var i = -1;
|
||||
var byteUnits = [" kB", " MB", " GB", " TB", "PB", "EB", "ZB", "YB"];
|
||||
do {
|
||||
|
@ -1,37 +1,35 @@
|
||||
import React from "react"
|
||||
import React, { useState, useEffect } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
||||
import { Map, List } from "immutable"
|
||||
import Loader from "../loader/loader"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
const mapStateToProps = (state) => ({
|
||||
searching: state.torrentStore.get("searching"),
|
||||
results: state.torrentStore.get("searchResults"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent, searchTorrents }, dispatch)
|
||||
});
|
||||
const mapDispatchToProps = { addTorrent, searchTorrents };
|
||||
|
||||
const TorrentSearch = (props) => {
|
||||
const [search, setSearch] = useState(props.match.params.search || "");
|
||||
const [type, setType] = useState(props.match.params.type || "");
|
||||
const [url, setUrl] = useState("");
|
||||
|
||||
const getUrl = () =>
|
||||
`/torrents/search/${type}/${encodeURI(search)}`;
|
||||
|
||||
useEffect(() => {
|
||||
if (search === "") { return }
|
||||
if (type === "") { return }
|
||||
|
||||
const url = getUrl();
|
||||
props.searchTorrents(url)
|
||||
props.history.push(url);
|
||||
}, [url]);
|
||||
|
||||
class TorrentSearch extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSearchInput = this.handleSearchInput.bind(this);
|
||||
this.state = { search: (this.props.router.params.search || "") };
|
||||
}
|
||||
handleSearchInput() {
|
||||
this.setState({ search: this.refs.search.value });
|
||||
}
|
||||
handleClick(type) {
|
||||
if (this.state.search === "") { return }
|
||||
const url = `/torrents/search/${type}/${encodeURI(this.state.search)}`;
|
||||
this.props.router.push(url);
|
||||
}
|
||||
render() {
|
||||
const searchFromURL = this.props.router.params.search || "";
|
||||
const typeFromURL = this.props.router.params.type || "";
|
||||
return (
|
||||
<div>
|
||||
<div className="col-xs-12">
|
||||
@ -41,9 +39,8 @@ class TorrentSearch extends React.PureComponent {
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Search torrents"
|
||||
value={this.state.search}
|
||||
onChange={this.handleSearchInput}
|
||||
ref="search"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
@ -52,32 +49,44 @@ class TorrentSearch extends React.PureComponent {
|
||||
<SearchButton
|
||||
text="Search movies"
|
||||
type="movies"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("movies")}
|
||||
/>
|
||||
typeFromURL={type}
|
||||
handleClick={() => {
|
||||
setType("movies");
|
||||
setUrl(getUrl());
|
||||
}}/>
|
||||
<SearchButton
|
||||
text="Search shows"
|
||||
type="shows"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("shows")}
|
||||
/>
|
||||
typeFromURL={type}
|
||||
handleClick={() => {
|
||||
setType("shows");
|
||||
setUrl(getUrl());
|
||||
}}/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<TorrentList
|
||||
searching={this.props.searching}
|
||||
results={this.props.results}
|
||||
addTorrent={this.props.addTorrent}
|
||||
searchFromURL={searchFromURL}
|
||||
searching={props.searching}
|
||||
results={props.results}
|
||||
addTorrent={props.addTorrent}
|
||||
searchFromURL={search}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
TorrentSearch.propTypes = {
|
||||
searching: PropTypes.bool.isRequired,
|
||||
results: PropTypes.instanceOf(List),
|
||||
searchFromURL: PropTypes.string,
|
||||
match: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
addTorrent: PropTypes.func.isRequired,
|
||||
searchTorrents: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
function SearchButton(props) {
|
||||
const SearchButton = (props) => {
|
||||
const color = (props.type === props.typeFromURL) ? "primary" : "default";
|
||||
return (
|
||||
<div className="col-xs-6">
|
||||
@ -92,8 +101,14 @@ function SearchButton(props) {
|
||||
|
||||
);
|
||||
}
|
||||
SearchButton.propTypes = {
|
||||
type: PropTypes.string,
|
||||
typeFromURL: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
handleClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function TorrentList(props) {
|
||||
const TorrentList = (props) => {
|
||||
if (props.searching) {
|
||||
return (<Loader />);
|
||||
}
|
||||
@ -125,9 +140,14 @@ function TorrentList(props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
TorrentList.propTypes = {
|
||||
searching: PropTypes.bool.isRequired,
|
||||
results: PropTypes.instanceOf(List),
|
||||
searchFromURL: PropTypes.string,
|
||||
addTorrent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function Torrent(props) {
|
||||
return (
|
||||
const Torrent = (props) => (
|
||||
<div className="row">
|
||||
<div className="col-xs-12">
|
||||
<table className="table responsive table-align-middle torrent-search-result">
|
||||
@ -168,9 +188,12 @@ function Torrent(props) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Torrent.propTypes = {
|
||||
data: PropTypes.instanceOf(Map),
|
||||
addTorrent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function TorrentHealth(props) {
|
||||
const TorrentHealth = (props) => {
|
||||
const seeders = props.seeders || 0;
|
||||
const leechers = props.leechers || 1;
|
||||
|
||||
@ -208,5 +231,10 @@ function TorrentHealth(props) {
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
TorrentHealth.propTypes = {
|
||||
url: PropTypes.string,
|
||||
seeders: PropTypes.number,
|
||||
leechers: PropTypes.number,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
||||
|
@ -1,6 +1,22 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { Redirect, Link } from "react-router-dom"
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isActivated: state.userStore.get("isActivated"),
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
});
|
||||
|
||||
const UserActivation = (props) => {
|
||||
if (!props.isLogged) {
|
||||
return (<Redirect to="/users/login"/>);
|
||||
}
|
||||
|
||||
if (props.isActivated) {
|
||||
return (<Redirect to="/"/>);
|
||||
}
|
||||
|
||||
function UserActivation(props) {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
@ -8,9 +24,15 @@ function UserActivation(props) {
|
||||
<h2>Waiting for activation</h2>
|
||||
<hr />
|
||||
<h3>Hang tight! Your user will soon be activated by the administrators of this site.</h3>
|
||||
<Link to="/users/logout">Logout</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default UserActivation;
|
||||
UserActivation.propTypes = {
|
||||
isActivated: PropTypes.bool.isRequired,
|
||||
isLogged: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(UserActivation);
|
||||
|
@ -1,72 +1,73 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { Redirect, Link } from "react-router-dom"
|
||||
|
||||
import { loginUser } from "../../actions/users"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
const mapStateToProps = (state) => ({
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
loading: state.userStore.get("loading"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ loginUser }, dispatch)
|
||||
isLoading: state.userStore.get("loading"),
|
||||
error: state.userStore.get("error"),
|
||||
});
|
||||
const mapDispatchToProps = { loginUser };
|
||||
|
||||
class UserLoginForm extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.isLogged) {
|
||||
return
|
||||
}
|
||||
if (!nextProps.location.query.redirect) {
|
||||
// Redirect home
|
||||
nextProps.router.push("/");
|
||||
} else {
|
||||
// Redirect to the previous page
|
||||
nextProps.router.push(nextProps.location.query.redirect);
|
||||
}
|
||||
}
|
||||
handleSubmit(e) {
|
||||
const UserLoginForm = (props) => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (this.props.loading) {
|
||||
return;
|
||||
if (!props.isLoading) {
|
||||
props.loginUser(username, password);
|
||||
}
|
||||
const username = this.refs.username.value;
|
||||
const password = this.refs.password.value;
|
||||
this.props.loginUser(username, password);
|
||||
}
|
||||
render() {
|
||||
|
||||
if (props.isLogged) {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
<div className="col-md-6 col-md-offset-3 col-xs-12">
|
||||
<h2>Log in</h2>
|
||||
<hr/>
|
||||
<form ref="loginForm" className="form-horizontal" onSubmit={(e) => this.handleSubmit(e)}>
|
||||
{props.error && props.error !== "" &&
|
||||
<div className="alert alert-danger">
|
||||
{props.error}
|
||||
</div>
|
||||
}
|
||||
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
|
||||
<div>
|
||||
<label htmlFor="user_email">Username</label>
|
||||
<label>Username</label>
|
||||
<br/>
|
||||
<input className="form-control" type="username" ref="username"/>
|
||||
<input className="form-control" type="username" autoFocus
|
||||
value={username} onChange={(e) => setUsername(e.target.value)}/>
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="user_password">Password</label>
|
||||
<label>Password</label>
|
||||
<br/>
|
||||
<input className="form-control" type="password" ref="password"/>
|
||||
<input className="form-control" type="password" autoComplete="new-password"
|
||||
value={password} onChange={(e) => setPassword(e.target.value)}/>
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
{this.props.loading &&
|
||||
<span className="text-muted">
|
||||
<small>
|
||||
No account yet ? <Link to="/users/signup">Create one</Link>
|
||||
</small>
|
||||
</span>
|
||||
{props.isLoading &&
|
||||
<button className="btn btn-primary pull-right">
|
||||
<i className="fa fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
}
|
||||
{this.props.loading ||
|
||||
{props.isLoading ||
|
||||
<span className="spaced-icons">
|
||||
<input className="btn btn-primary pull-right" type="submit" value="Log in"/>
|
||||
</span>
|
||||
}
|
||||
<br/>
|
||||
</div>
|
||||
@ -76,5 +77,11 @@ class UserLoginForm extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
UserLoginForm.propTypes = {
|
||||
loginUser: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
isLogged: PropTypes.bool.isRequired,
|
||||
error: PropTypes.string,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm);
|
||||
|
28
frontend/js/components/users/logout.js
Normal file
28
frontend/js/components/users/logout.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { Redirect } from "react-router-dom"
|
||||
|
||||
import { userLogout } from "../../actions/users"
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
});
|
||||
const mapDispatchToProps = { userLogout };
|
||||
|
||||
const UserLogout = (props) => {
|
||||
if (props.isLogged) {
|
||||
props.userLogout();
|
||||
}
|
||||
|
||||
return (
|
||||
<Redirect to="/users/login" />
|
||||
);
|
||||
}
|
||||
UserLogout.propTypes = {
|
||||
isLogged: PropTypes.bool.isRequired,
|
||||
userLogout: PropTypes.func.isRequired,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserLogout);
|
@ -1,86 +1,95 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import Loader from "../loader/loader"
|
||||
import { Map } from "immutable"
|
||||
|
||||
import { updateUser } from "../../actions/users"
|
||||
import {
|
||||
updateUser, getUserInfos,
|
||||
getUserTokens, getUserModules
|
||||
} from "../../actions/users"
|
||||
import Modules from "../modules/modules"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
polochonToken: state.userStore.get("polochonToken"),
|
||||
polochonUrl: state.userStore.get("polochonUrl"),
|
||||
const mapStateToProps = (state) => ({
|
||||
isLoading: state.userStore.get("loading"),
|
||||
token: state.userStore.get("polochonToken"),
|
||||
url: state.userStore.get("polochonUrl"),
|
||||
modules : state.userStore.get("modules"),
|
||||
};
|
||||
modulesLoading : state.userStore.get("modulesLoading"),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateUser, getUserInfos,
|
||||
getUserTokens, getUserModules
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ updateUser }, dispatch)
|
||||
const UserProfile = (props) => {
|
||||
const [fetched, setIsFetched] = useState(false);
|
||||
if (!fetched) {
|
||||
props.getUserInfos();
|
||||
props.getUserModules();
|
||||
setIsFetched(true);
|
||||
}
|
||||
|
||||
function UserProfile(props) {
|
||||
return (
|
||||
<div>
|
||||
<UserEdit
|
||||
polochonToken={props.polochonToken}
|
||||
polochonUrl={props.polochonUrl}
|
||||
isLoading={props.isLoading}
|
||||
token={props.token}
|
||||
url={props.url}
|
||||
updateUser={props.updateUser}
|
||||
/>
|
||||
<Modules modules={props.modules} />
|
||||
<Modules
|
||||
modules={props.modules}
|
||||
isLoading={props.modulesLoading}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
class UserEdit extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
polochonToken: props.polochonToken,
|
||||
polochonUrl: props.polochonUrl,
|
||||
UserProfile.propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
token: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
updateUser: PropTypes.func.isRequired,
|
||||
getUserInfos: PropTypes.func.isRequired,
|
||||
getUserModules: PropTypes.func.isRequired,
|
||||
modules: PropTypes.instanceOf(Map),
|
||||
modulesLoading: PropTypes.bool.isRequired,
|
||||
};
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleUrlInput = this.handleUrlInput.bind(this);
|
||||
this.handleTokenInput = this.handleTokenInput.bind(this);
|
||||
|
||||
const UserEdit = (props) => {
|
||||
if (props.isLoading) {
|
||||
return <Loader />
|
||||
}
|
||||
handleSubmit(ev) {
|
||||
|
||||
const [url, setUrl] = useState(props.url);
|
||||
const [token, setToken] = useState(props.token);
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||
|
||||
const handleSubmit = (ev) => {
|
||||
ev.preventDefault();
|
||||
this.props.updateUser({
|
||||
"polochon_url": this.refs.polochonUrl.value,
|
||||
"polochon_token": this.refs.polochonToken.value,
|
||||
"password": this.refs.newPassword.value,
|
||||
"password_confirm": this.refs.newPasswordConfirm.value,
|
||||
props.updateUser({
|
||||
"polochon_url": url,
|
||||
"polochon_token": token,
|
||||
"password": password,
|
||||
"password_confirm": passwordConfirm,
|
||||
});
|
||||
}
|
||||
handleTokenInput() {
|
||||
this.setState({ polochonToken: this.refs.polochonToken.value });
|
||||
}
|
||||
handleUrlInput() {
|
||||
this.setState({ polochonUrl: this.refs.polochonUrl.value });
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if ((nextProps.polochonUrl !== "")
|
||||
&& (this.state.polochonUrl === "")
|
||||
&& (nextProps.polochonToken !== "")
|
||||
&& (this.state.polochonToken === "")) {
|
||||
this.setState({
|
||||
polochonToken: nextProps.polochonToken,
|
||||
polochonUrl: nextProps.polochonUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
<div className="col-md-6 col-md-offset-3 col-xs-12">
|
||||
<h2>Edit user</h2>
|
||||
<hr />
|
||||
<form className="form-horizontal" onSubmit={(ev) => this.handleSubmit(ev)}>
|
||||
<form className="form-horizontal" onSubmit={(ev) => handleSubmit(ev)}>
|
||||
<div className="form-group">
|
||||
<label className="control-label">Polochon URL</label>
|
||||
<input
|
||||
className="form-control"
|
||||
value={this.state.polochonUrl}
|
||||
onChange={this.handleUrlInput}
|
||||
ref="polochonUrl"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -88,9 +97,8 @@ class UserEdit extends React.PureComponent {
|
||||
<label className="control-label">Polochon token</label>
|
||||
<input
|
||||
className="form-control"
|
||||
value={this.state.polochonToken}
|
||||
onChange={this.handleTokenInput}
|
||||
ref="polochonToken"
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -98,12 +106,24 @@ class UserEdit extends React.PureComponent {
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Password</label>
|
||||
<input type="password" autoComplete="off" ref="newPassword" className="form-control"/>
|
||||
<input
|
||||
className="form-control"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Confirm Password</label>
|
||||
<input type="password" autoComplete="off" ref="newPasswordConfirm" className="form-control"/>
|
||||
<input
|
||||
className="form-control"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
value={passwordConfirm}
|
||||
onChange={(e) => setPasswordConfirm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -115,5 +135,11 @@ class UserEdit extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
UserEdit.propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
token: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
updateUser: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
|
||||
|
@ -1,63 +1,76 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { Redirect } from "react-router-dom"
|
||||
|
||||
import { userSignUp } from "../../actions/users"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
const mapStateToProps = (state) => ({
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
};
|
||||
isLoading: state.userStore.get("loading"),
|
||||
error: state.userStore.get("error"),
|
||||
});
|
||||
const mapDispatchToProps = { userSignUp };
|
||||
|
||||
const UserSignUp = (props) => {
|
||||
if (props.isLogged) {
|
||||
return (<Redirect to="/"/>);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ userSignUp }, dispatch)
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||
|
||||
class UserSignUp extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
handleSubmit(e) {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.userSignUp({
|
||||
"username": this.refs.username.value,
|
||||
"password": this.refs.password.value,
|
||||
"password_confirm": this.refs.passwordConfirm.value,
|
||||
props.userSignUp({
|
||||
"username": username,
|
||||
"password": password,
|
||||
"password_confirm": passwordConfirm,
|
||||
});
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.isLogged) {
|
||||
return
|
||||
}
|
||||
// Redirect home
|
||||
nextProps.router.push("/");
|
||||
}
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
<div className="col-md-6 col-md-offset-3 col-xs-12">
|
||||
<h2>Sign up</h2>
|
||||
<hr />
|
||||
{props.error && props.error !== "" &&
|
||||
<div className="alert alert-danger">
|
||||
{props.error}
|
||||
</div>
|
||||
}
|
||||
|
||||
<form ref="userSignUpForm" className="form-horizontal" onSubmit={(e) => this.handleSubmit(e)}>
|
||||
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="form-group">
|
||||
<label className="control-label">Username</label>
|
||||
<input autoFocus="autofocus" className="form-control" type="text" ref="username" />
|
||||
<input autoFocus="autofocus" className="form-control"
|
||||
value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Password</label>
|
||||
<input className="form-control" ref="password" type="password" />
|
||||
<input className="form-control" type="password"
|
||||
value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Password confirm</label>
|
||||
<input className="form-control" ref="passwordConfirm" type="password" />
|
||||
<input className="form-control" type="password"
|
||||
value={passwordConfirm} onChange={(e) => setPasswordConfirm(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
{props.isLoading &&
|
||||
<button className="btn btn-primary pull-right">
|
||||
<i className="fa fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
}
|
||||
{props.isLoading ||
|
||||
<span>
|
||||
<input className="btn btn-primary pull-right" type="submit" value="Sign up"/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -65,5 +78,11 @@ class UserSignUp extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
UserSignUp.propTypes = {
|
||||
isLogged: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
userSignUp: PropTypes.func.isRequired,
|
||||
error: PropTypes.string,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserSignUp);
|
||||
|
@ -1,52 +1,45 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { connect } from "react-redux"
|
||||
import { UAParser } from "ua-parser-js"
|
||||
import moment from "moment"
|
||||
import { Map, List } from "immutable"
|
||||
|
||||
import { bindActionCreators } from "redux"
|
||||
import { deleteUserToken } from "../../actions/users"
|
||||
import { getUserTokens, deleteUserToken } from "../../actions/users"
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ deleteUserToken }, dispatch)
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
const mapStateToProps = (state) => ({
|
||||
tokens: state.userStore.get("tokens"),
|
||||
};
|
||||
});
|
||||
const mapDispatchToProps = { getUserTokens, deleteUserToken };
|
||||
|
||||
const UserTokens = (props) => {
|
||||
const [fetched, setIsFetched] = useState(false);
|
||||
if (!fetched) {
|
||||
props.getUserTokens();
|
||||
setIsFetched(true);
|
||||
}
|
||||
|
||||
function UserTokens(props) {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content-fluid">
|
||||
<TokenList {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TokenList(props) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="hidden-xs">Tokens</h2>
|
||||
<h3 className="visible-xs">Tokens</h3>
|
||||
|
||||
<div>
|
||||
{props.tokens.map(function(el, index) {
|
||||
return (
|
||||
<Token
|
||||
key={index}
|
||||
data={el}
|
||||
deleteToken={props.deleteUserToken}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{props.tokens.map((el, index) => (
|
||||
<Token key={index} data={el} deleteToken={props.deleteUserToken} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
UserTokens.propTypes = {
|
||||
tokens: PropTypes.instanceOf(List),
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
getUserTokens: PropTypes.func.isRequired,
|
||||
deleteUserToken: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function Token(props) {
|
||||
const Token = (props) => {
|
||||
const ua = UAParser(props.data.get("user_agent"));
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
@ -81,29 +74,30 @@ function Token(props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Token.propTypes = {
|
||||
data: PropTypes.instanceOf(Map).isRequired,
|
||||
};
|
||||
|
||||
class Actions extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
const Actions = (props) => {
|
||||
const handleClick = () => {
|
||||
props.deleteToken(props.data.get("token"));
|
||||
}
|
||||
handleClick() {
|
||||
const token = this.props.data.get("token");
|
||||
this.props.deleteToken(token);
|
||||
}
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="col-xs-1">
|
||||
<span
|
||||
className="fa fa-trash fa-lg pull-right clickable user-token-action"
|
||||
onClick={this.handleClick}>
|
||||
onClick={() => handleClick()}>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Actions.propTypes = {
|
||||
data: PropTypes.instanceOf(Map).isRequired,
|
||||
deleteToken: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function Logo(props) {
|
||||
const Logo = (props) => {
|
||||
var className;
|
||||
if (props.ua === "canape-cli") {
|
||||
className = "terminal";
|
||||
@ -141,7 +135,7 @@ function Logo(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function OS(props) {
|
||||
const OS = (props) => {
|
||||
var osName = "-";
|
||||
|
||||
if (props.name !== undefined) {
|
||||
@ -157,7 +151,7 @@ function OS(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function Device(props) {
|
||||
const Device = (props) => {
|
||||
var deviceName = "-";
|
||||
|
||||
if (props.model !== undefined) {
|
||||
@ -169,7 +163,7 @@ function Device(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function Browser(props) {
|
||||
const Browser = (props) => {
|
||||
var browserName = "-";
|
||||
if (props.name !== undefined) {
|
||||
browserName = props.name;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Map, List, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
export const defaultState = Map({
|
||||
"users": List(),
|
||||
"stats": Map({}),
|
||||
"modules": Map({}),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { combineReducers } from "redux";
|
||||
import { routerReducer } from "react-router-redux"
|
||||
|
||||
import movieStore from "./movies"
|
||||
import showsStore from "./shows"
|
||||
@ -9,8 +8,7 @@ import alerts from "./alerts"
|
||||
import torrentStore from "./torrents"
|
||||
import adminStore from "./admins"
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
routing: routerReducer,
|
||||
export default combineReducers({
|
||||
movieStore,
|
||||
showsStore,
|
||||
showStore,
|
||||
@ -18,6 +16,4 @@ const rootReducer = combineReducers({
|
||||
alerts,
|
||||
torrentStore,
|
||||
adminStore,
|
||||
})
|
||||
|
||||
export default rootReducer;
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ const defaultState = Map({
|
||||
|
||||
const handlers = {
|
||||
"MOVIE_LIST_FETCH_PENDING": state => state.set("loading", true),
|
||||
"MOVIE_LIST_FETCH_ERROR": state => state.set("loading", false),
|
||||
"MOVIE_LIST_FETCH_FULFILLED": (state, action) => {
|
||||
let movies = Map();
|
||||
action.payload.response.data.map(function (movie) {
|
||||
|
@ -4,45 +4,65 @@ import jwtDecode from "jwt-decode"
|
||||
import Cookies from "universal-cookie"
|
||||
|
||||
const defaultState = Map({
|
||||
error: "",
|
||||
loading: false,
|
||||
username: "",
|
||||
isAdmin: false,
|
||||
isActivated: false,
|
||||
isTokenSet: false,
|
||||
isLogged: false,
|
||||
polochonToken: "",
|
||||
polochonUrl: "",
|
||||
tokens: List(),
|
||||
modules: Map(),
|
||||
modulesLoading: false,
|
||||
});
|
||||
|
||||
const handlers = {
|
||||
"USER_LOGIN_PENDING": state => state.set("loading", true),
|
||||
"USER_LOGIN_ERROR": state => {
|
||||
state.set("loading", false)
|
||||
return logoutUser()
|
||||
"USER_LOGIN_ERROR": (state, action) => {
|
||||
state.set("loading", false);
|
||||
return logoutUser(action.payload.response.message);
|
||||
},
|
||||
"USER_LOGIN_FULFILLED": (state, action) => {
|
||||
return updateFromToken(state, action.payload.response.data.token)
|
||||
},
|
||||
"USER_SIGNUP_PENDING": state => state.set("loading", true),
|
||||
"USER_SIGNUP_ERROR": (state, action) => state.merge(Map({
|
||||
error: action.payload.response.message,
|
||||
loading: false,
|
||||
})),
|
||||
"USER_SIGNUP_FULFILLED": state => state.merge(Map({error: "", loading: false})),
|
||||
"USER_SET_TOKEN": (state, action) => updateFromToken(state, action.payload.token),
|
||||
"USER_LOGOUT": () => logoutUser(),
|
||||
"GET_USER_PENDING": state => state.set("loading", true),
|
||||
"GET_USER_FULFILLED": (state, action) => state.merge(Map({
|
||||
polochonToken: action.payload.response.data.token,
|
||||
polochonUrl: action.payload.response.data.url,
|
||||
loading: false,
|
||||
})),
|
||||
"GET_USER_TOKENS_PENDING": state => state.set("loading", true),
|
||||
"GET_USER_TOKENS_FULFILLED": (state, action) => state.merge(Map({
|
||||
"tokens": fromJS(action.payload.response.data),
|
||||
"loading": false,
|
||||
})),
|
||||
"GET_USER_MODULES_PENDING": state => state.set("modulesLoading", true),
|
||||
"GET_USER_MODULES_FULFILLED": (state, action) => state.merge(Map({
|
||||
"modules": fromJS(action.payload.response.data),
|
||||
"modulesLoading": false,
|
||||
})),
|
||||
"GET_USER_TOKENS_FULFILLED": (state, action) => state.set(
|
||||
"tokens", fromJS(action.payload.response.data)
|
||||
),
|
||||
"GET_USER_MODULES_FULFILLED": (state, action) => state.set(
|
||||
"modules", fromJS(action.payload.response.data)
|
||||
),
|
||||
}
|
||||
|
||||
function logoutUser() {
|
||||
function logoutUser(error) {
|
||||
localStorage.removeItem("token");
|
||||
const cookies = new Cookies();
|
||||
cookies.remove("token");
|
||||
if (error !== "") {
|
||||
return defaultState.set("error", error);
|
||||
} else {
|
||||
return defaultState
|
||||
}
|
||||
}
|
||||
|
||||
function updateFromToken(state, token) {
|
||||
const decodedToken = jwtDecode(token);
|
||||
@ -52,8 +72,9 @@ function updateFromToken(state, token) {
|
||||
cookies.set("token", token);
|
||||
|
||||
return state.merge(Map({
|
||||
userLoading: false,
|
||||
error: "",
|
||||
isLogged: true,
|
||||
isTokenSet: true,
|
||||
isAdmin: decodedToken.isAdmin,
|
||||
isActivated: decodedToken.isActivated,
|
||||
username: decodedToken.username,
|
||||
|
@ -29,7 +29,8 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
||||
main: mainPayload,
|
||||
}
|
||||
})
|
||||
promise
|
||||
|
||||
return promise
|
||||
.then(response => {
|
||||
if (response.data.status === "error")
|
||||
{
|
||||
|
@ -1,292 +0,0 @@
|
||||
import MovieList from "./components/movies/list"
|
||||
import ShowList from "./components/shows/list"
|
||||
import ShowDetails from "./components/shows/details"
|
||||
import UserLoginForm from "./components/users/login"
|
||||
import UserProfile from "./components/users/profile"
|
||||
import UserTokens from "./components/users/tokens"
|
||||
import UserActivation from "./components/users/activation"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
import TorrentSearch from "./components/torrents/search"
|
||||
import AdminPanel from "./components/admins/panel"
|
||||
|
||||
import { fetchTorrents, searchTorrents } from "./actions/torrents"
|
||||
import { userLogout, getUserInfos, getUserTokens, getUserModules } from "./actions/users"
|
||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||
import { getUsers, getStats, getAdminModules } from "./actions/admins"
|
||||
|
||||
import store from "./store"
|
||||
|
||||
// Default route
|
||||
const defaultRoute = "/movies/explore/yts/seeds";
|
||||
|
||||
// This function returns true if the user is logged in, false otherwise
|
||||
function isLoggedIn() {
|
||||
const state = store.getState();
|
||||
const isLogged = state.userStore.get("isLogged");
|
||||
let token = localStorage.getItem("token");
|
||||
|
||||
// Let's check if the user has a token, if he does let's assume he's logged
|
||||
// in. If that's not the case he will be logged out on the fisrt query
|
||||
if (token && token !== "") {
|
||||
store.dispatch({
|
||||
type: "USER_SET_TOKEN",
|
||||
payload: {
|
||||
token: token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isLogged || (token && token !== "")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function isActivated() {
|
||||
const state = store.getState();
|
||||
return state.userStore.get("isActivated");
|
||||
}
|
||||
|
||||
var pollingTorrentsId;
|
||||
const loginCheck = function(nextState, replace, next, f = null) {
|
||||
const loggedIn = isLoggedIn();
|
||||
if (!loggedIn) {
|
||||
replace("/users/login");
|
||||
} else if (!isActivated()) {
|
||||
replace("/users/activation");
|
||||
} else {
|
||||
if (f) { f(); }
|
||||
|
||||
// Poll torrents once logged
|
||||
if (!pollingTorrentsId) {
|
||||
// Fetch the torrents every 10s
|
||||
pollingTorrentsId = setInterval(function() {
|
||||
store.dispatch(fetchTorrents());
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const adminCheck = function(nextState, replace, next, f = null) {
|
||||
const state = store.getState();
|
||||
const isAdmin = state.userStore.get("isAdmin");
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
if (!isAdmin) { replace(defaultRoute); }
|
||||
if (f) { f(); }
|
||||
next();
|
||||
})
|
||||
}
|
||||
|
||||
export default function getRoutes(App) {
|
||||
return {
|
||||
path: "/",
|
||||
component: App,
|
||||
indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)},
|
||||
childRoutes: [
|
||||
{
|
||||
path: "/users/signup",
|
||||
component: UserSignUp,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (isLoggedIn()) {
|
||||
// User is already logged in, redirect him to the default route
|
||||
replace(defaultRoute);
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/login",
|
||||
component: UserLoginForm,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (isLoggedIn()) {
|
||||
// User is already logged in, redirect him to the default route
|
||||
replace(defaultRoute);
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/profile",
|
||||
component: UserProfile,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(getUserInfos());
|
||||
store.dispatch(getUserModules());
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/tokens",
|
||||
component: UserTokens,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(getUserTokens());
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/activation",
|
||||
component: UserActivation,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (!isLoggedIn()) {
|
||||
replace("/users/login");
|
||||
}
|
||||
if (isActivated()) {
|
||||
// User is already activated, redirect him to the default route
|
||||
replace(defaultRoute);
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/users/logout",
|
||||
onEnter: function(nextState, replace, next) {
|
||||
// Stop polling
|
||||
if (pollingTorrentsId !== null) {
|
||||
clearInterval(pollingTorrentsId);
|
||||
pollingTorrentsId = null;
|
||||
}
|
||||
store.dispatch(userLogout());
|
||||
replace("/users/login");
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/movies/search/:search",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies(`/movies/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/movies/polochon",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies("/movies/polochon"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/movies/explore/:source/:category",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (state.movieStore.get("exploreOptions").size === 0) {
|
||||
store.dispatch(getMovieExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchMovies(
|
||||
`/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/movies/wishlist",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies("/wishlist/movies"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/shows/search/:search",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows(`/shows/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/shows/polochon",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows("/shows/polochon"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/shows/wishlist",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows("/wishlist/shows"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/shows/details/:imdbId",
|
||||
component: ShowDetails,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShowDetails(nextState.params.imdbId));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/shows/explore/:source/:category",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (state.showsStore.get("exploreOptions").size === 0) {
|
||||
store.dispatch(getShowExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchShows(
|
||||
`/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/list",
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchTorrents());
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search/:type/:search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(searchTorrents(`/torrents/search/${nextState.params.type}/${encodeURI(nextState.params.search)}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
component: AdminPanel,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
adminCheck(nextState, replace, next, function() {
|
||||
store.dispatch(getUsers());
|
||||
store.dispatch(getStats());
|
||||
store.dispatch(getAdminModules());
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
@ -1,15 +1,12 @@
|
||||
import { createHashHistory } from "history";
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
import { syncHistoryWithStore } from "react-router-redux"
|
||||
import { hashHistory } from "react-router"
|
||||
import thunk from "redux-thunk"
|
||||
import { routerMiddleware } from "react-router-redux"
|
||||
import thunk from "redux-thunk";
|
||||
|
||||
// Import the root reducer
|
||||
import rootReducer from "./reducers/index"
|
||||
export const history = createHashHistory();
|
||||
|
||||
const routingMiddleware = routerMiddleware(hashHistory)
|
||||
import rootReducer from "./reducers/index";
|
||||
|
||||
const middlewares = [thunk, routingMiddleware];
|
||||
const middlewares = [thunk];
|
||||
|
||||
// Only use in development mode (set in webpack)
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
@ -18,9 +15,14 @@ if (process.env.NODE_ENV === "development") {
|
||||
}
|
||||
|
||||
// Export the store
|
||||
const store = compose(applyMiddleware(...middlewares))(createStore)(rootReducer);
|
||||
|
||||
// Sync history with store
|
||||
export const history = syncHistoryWithStore(hashHistory, store);
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
compose(
|
||||
applyMiddleware(
|
||||
...middlewares,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
export default store;
|
||||
|
||||
|
@ -23,10 +23,10 @@
|
||||
"react-dom": "^16.2.0",
|
||||
"react-infinite-scroller": "^1.0.4",
|
||||
"react-loading": "2.0.3",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "^3.2.0",
|
||||
"react-router-bootstrap": "^0.23.1",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"react-redux": "6.0.1",
|
||||
"react-router": "5.0.0",
|
||||
"react-router-bootstrap": "^0.25.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"redux": "^4.0.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.2.0",
|
||||
|
144
yarn.lock
144
yarn.lock
@ -651,7 +651,7 @@
|
||||
core-js "^2.6.5"
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.1.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d"
|
||||
integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==
|
||||
@ -1866,14 +1866,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
create-react-class@^15.5.1:
|
||||
version "15.6.3"
|
||||
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
|
||||
integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==
|
||||
create-react-context@^0.2.2:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
|
||||
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
|
||||
dependencies:
|
||||
fbjs "^0.8.9"
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
fbjs "^0.8.0"
|
||||
gud "^1.0.0"
|
||||
|
||||
cross-spawn@^5.0.1:
|
||||
version "5.1.0"
|
||||
@ -2658,7 +2657,7 @@ fastparse@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
|
||||
integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==
|
||||
|
||||
fbjs@^0.8.9:
|
||||
fbjs@^0.8.0:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
@ -2992,6 +2991,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
|
||||
gud@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
|
||||
integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
|
||||
|
||||
har-schema@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
|
||||
@ -3114,17 +3118,7 @@ hawk@~3.1.3:
|
||||
hoek "2.x.x"
|
||||
sntp "1.x.x"
|
||||
|
||||
history@^3.0.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c"
|
||||
integrity sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw=
|
||||
dependencies:
|
||||
invariant "^2.2.1"
|
||||
loose-envify "^1.2.0"
|
||||
query-string "^4.2.2"
|
||||
warning "^3.0.0"
|
||||
|
||||
history@^4.7.2:
|
||||
history@^4.7.2, history@^4.9.0:
|
||||
version "4.9.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca"
|
||||
integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==
|
||||
@ -3150,12 +3144,7 @@ hoek@2.x.x:
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=
|
||||
|
||||
hoist-non-react-statics@^2.3.1:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
|
||||
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
|
||||
|
||||
hoist-non-react-statics@^3.1.0:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
|
||||
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
|
||||
@ -3324,7 +3313,7 @@ interpret@^1.0.0, interpret@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
|
||||
integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
|
||||
|
||||
invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
|
||||
invariant@^2.2.2, invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
@ -3551,6 +3540,11 @@ is-windows@^1.0.1, is-windows@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@ -4673,6 +4667,13 @@ path-parse@^1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
||||
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
|
||||
@ -5101,7 +5102,7 @@ prop-types-extra@^1.0.1:
|
||||
react-is "^16.3.2"
|
||||
warning "^3.0.0"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@ -5192,7 +5193,7 @@ qs@~6.5.2:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
query-string@^4.1.0, query-string@^4.2.2:
|
||||
query-string@^4.1.0:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
||||
integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s=
|
||||
@ -5286,12 +5287,12 @@ react-infinite-scroller@^1.0.4:
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||
react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2:
|
||||
version "16.8.6"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
||||
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
|
||||
|
||||
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
@ -5320,43 +5321,53 @@ react-prop-types@^0.4.0:
|
||||
dependencies:
|
||||
warning "^3.0.0"
|
||||
|
||||
react-redux@^5.0.6:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.1.tgz#88e368682c7fa80e34e055cd7ac56f5936b0f52f"
|
||||
integrity sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==
|
||||
react-redux@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
|
||||
integrity sha512-T52I52Kxhbqy/6TEfBv85rQSDz6+Y28V/pf52vDWs1YRXG19mcFOGfHnY2HsNFHyhP+ST34Aih98fvt6tqwVcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
invariant "^2.2.4"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.8.2"
|
||||
|
||||
react-router-bootstrap@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-bootstrap/-/react-router-bootstrap-0.25.0.tgz#5d1a99b5b8a2016c011fc46019d2397e563ce0df"
|
||||
integrity sha512-/22eqxjn6Zv5fvY2rZHn57SKmjmJfK7xzJ6/G1OgxAjLtKVfWgV5sn41W2yiqzbtV5eE4/i4LeDLBGYTqx7jbA==
|
||||
dependencies:
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-router-dom@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073"
|
||||
integrity sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.0.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.0.tgz#349863f769ffc2fa10ee7331a4296e86bc12879d"
|
||||
integrity sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
create-react-context "^0.2.2"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
invariant "^2.2.4"
|
||||
loose-envify "^1.1.0"
|
||||
prop-types "^15.6.1"
|
||||
loose-envify "^1.3.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.6.0"
|
||||
react-lifecycles-compat "^3.0.0"
|
||||
|
||||
react-router-bootstrap@^0.23.1:
|
||||
version "0.23.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router-bootstrap/-/react-router-bootstrap-0.23.3.tgz#970c35c53c04c61fb6b110d4ff651a7e8a73b2ba"
|
||||
integrity sha1-lww1xTwExh+2sRDU/2Uafopzsro=
|
||||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-router-redux@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.8.tgz#227403596b5151e182377dab835b5d45f0f8054e"
|
||||
integrity sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=
|
||||
|
||||
react-router@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.1.tgz#b9a3279962bdfbe684c8bd0482b81ef288f0f244"
|
||||
integrity sha512-SXkhC0nr3G0ltzVU07IN8jYl0bB6FsrDIqlLC9dK3SITXqyTJyM7yhXlUqs89w3Nqi5OkXsfRUeHX+P874HQrg==
|
||||
dependencies:
|
||||
create-react-class "^15.5.1"
|
||||
history "^3.0.0"
|
||||
hoist-non-react-statics "^2.3.1"
|
||||
invariant "^2.2.1"
|
||||
loose-envify "^1.2.0"
|
||||
prop-types "^15.5.6"
|
||||
warning "^3.0.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-transition-group@^2.0.0, react-transition-group@^2.2.0:
|
||||
version "2.9.0"
|
||||
@ -5438,6 +5449,11 @@ reduce-function-call@^1.0.1:
|
||||
dependencies:
|
||||
balanced-match "^0.4.2"
|
||||
|
||||
redux-devtools-extension@^2.13.8:
|
||||
version "2.13.8"
|
||||
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1"
|
||||
integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==
|
||||
|
||||
redux-logger@^3.0.6:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
|
||||
|
Loading…
x
Reference in New Issue
Block a user