Compare commits

..

16 Commits

Author SHA1 Message Date
817da07a2d Lint the js on drone builds
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-04-03 16:57:54 +02:00
af2641c317 Use redux hooks on the websocket component
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-04-03 16:49:12 +02:00
d9fae3e23a Use redux hooks on the navbar component 2020-04-03 16:49:12 +02:00
e7f96a1bd7 Use redux hooks on the auth component 2020-04-03 16:29:06 +02:00
2e83c3169f Use redux hooks on movies components 2020-04-03 16:24:00 +02:00
6ac382b659 Use redux hooks on buttons components 2020-04-03 16:19:12 +02:00
81f497170f Use redux hooks on admin components 2020-04-03 16:13:56 +02:00
6cfee5ea74 Use redux hooks on alert components 2020-04-03 16:13:56 +02:00
ea62b1c6ec Use redux hooks on notification components 2020-04-03 16:13:56 +02:00
d998d2838d Use redux hooks on shows components 2020-04-03 15:51:19 +02:00
27f5c5d558 Use redux hooks on torrent components 2020-04-03 15:51:19 +02:00
27f0b742a4 Use redux hooks on user components 2020-04-03 15:51:19 +02:00
ac0d746cc9 Use redux hooks on polochon components 2020-04-03 15:51:19 +02:00
c5336c477a Remove the custom routes for movies and shows 2020-04-03 15:51:19 +02:00
834ee8bcfc Let the show fetch its own details 2020-04-03 15:51:19 +02:00
5f7d402614 Move the fetch of the explorer options to its own component 2020-04-03 15:51:19 +02:00
40 changed files with 666 additions and 968 deletions

View File

@ -12,6 +12,7 @@ steps:
commands: commands:
- cd frontend - cd frontend
- npm install - npm install
- npm run-script lint
- npm run-script build - npm run-script build
- name: backend - name: backend

View File

@ -28,12 +28,10 @@ import { AdminPanel } from "./components/admins/panel";
import { Notifications } from "./components/notifications/notifications"; import { Notifications } from "./components/notifications/notifications";
import Alert from "./components/alerts/alert"; import Alert from "./components/alerts/alert";
import MovieList from "./components/movies/list"; import MovieList from "./components/movies/list";
import MoviesRoute from "./components/movies/route";
import NavBar from "./components/navbar"; import NavBar from "./components/navbar";
import WsHandler from "./components/websocket"; import WsHandler from "./components/websocket";
import { ShowDetails } from "./components/shows/details"; import { ShowDetails } from "./components/shows/details";
import ShowList from "./components/shows/list"; import ShowList from "./components/shows/list";
import ShowsRoute from "./components/shows/route";
import TorrentList from "./components/torrents/list"; import TorrentList from "./components/torrents/list";
import TorrentSearch from "./components/torrents/search"; import TorrentSearch from "./components/torrents/search";
import UserActivation from "./components/users/activation"; import UserActivation from "./components/users/activation";
@ -61,31 +59,23 @@ const App = () => (
exact exact
component={TorrentSearch} component={TorrentSearch}
/> />
<MoviesRoute path="/movies/polochon" exact component={MovieList} /> <Route path="/movies/polochon" exact component={MovieList} />
<MoviesRoute path="/movies/wishlist" exact component={MovieList} /> <Route path="/movies/wishlist" exact component={MovieList} />
<MoviesRoute <Route path="/movies/search/:search" exact component={MovieList} />
path="/movies/search/:search" <Route
exact
component={MovieList}
/>
<MoviesRoute
path="/movies/explore/:source/:category" path="/movies/explore/:source/:category"
exact exact
component={MovieList} component={MovieList}
/> />
<ShowsRoute path="/shows/polochon" exact component={ShowList} /> <Route path="/shows/polochon" exact component={ShowList} />
<ShowsRoute path="/shows/wishlist" exact component={ShowList} /> <Route path="/shows/wishlist" exact component={ShowList} />
<ShowsRoute path="/shows/search/:search" exact component={ShowList} /> <Route path="/shows/search/:search" exact component={ShowList} />
<ShowsRoute <Route
path="/shows/explore/:source/:category" path="/shows/explore/:source/:category"
exact exact
component={ShowList} component={ShowList}
/> />
<ShowsRoute <Route path="/shows/details/:imdbId" exact component={ShowDetails} />
path="/shows/details/:imdbId"
exact
component={ShowDetails}
/>
<Route render={() => <Redirect to="/movies/explore/yts/seeds" />} /> <Route render={() => <Redirect to="/movies/explore/yts/seeds" />} />
</Switch> </Switch>
</Container> </Container>

View File

@ -1,17 +1,19 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Route, Redirect } from "react-router-dom"; import { Route, Redirect } from "react-router-dom";
import { setUserToken } from "./actions/users"; import { setUserToken } from "./actions/users";
const protectedRoute = ({ export const ProtectedRoute = ({ component: Component, ...otherProps }) => {
component: Component, const dispatch = useDispatch();
isLogged,
isActivated, const isLogged = useSelector((state) => state.userStore.get("isLogged"));
isTokenSet, const isActivated = useSelector((state) =>
setUserToken, state.userStore.get("isActivated")
...otherProps );
}) => { const isTokenSet = useSelector((state) => state.userStore.get("isTokenSet"));
const isAuthenticated = () => { const isAuthenticated = () => {
if (isTokenSet) { if (isTokenSet) {
return true; return true;
@ -20,7 +22,7 @@ const protectedRoute = ({
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (isLogged || (token && token !== "")) { if (isLogged || (token && token !== "")) {
if (!isTokenSet) { if (!isTokenSet) {
setUserToken(token); dispatch(setUserToken(token));
} }
return true; return true;
} }
@ -45,24 +47,12 @@ const protectedRoute = ({
/> />
); );
}; };
protectedRoute.propTypes = { ProtectedRoute.propTypes = {
component: PropTypes.func, 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 }) => { export const AdminRoute = ({ component: Component, ...otherProps }) => {
const isAdmin = useSelector((state) => state.userStore.get("isAdmin"));
return ( return (
<Route <Route
{...otherProps} {...otherProps}
@ -76,10 +66,6 @@ const adminRoute = ({ component: Component, isAdmin, ...otherProps }) => {
/> />
); );
}; };
adminRoute.propTypes = { AdminRoute.propTypes = {
component: PropTypes.func, component: PropTypes.func,
isAdmin: PropTypes.bool.isRequired,
}; };
export const AdminRoute = connect((state) => ({
isAdmin: state.userStore.get("isLogged"),
}))(adminRoute);

View File

@ -1,28 +1,20 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { getAdminModules } from "../../actions/admins"; import { getAdminModules } from "../../actions/admins";
import Modules from "../modules/modules"; import Modules from "../modules/modules";
const AdminModulesConnected = ({ modules, loading, getAdminModules }) => { export const AdminModules = () => {
const dispatch = useDispatch();
const loading = useSelector((state) =>
state.adminStore.get("fetchingModules")
);
const modules = useSelector((state) => state.adminStore.get("modules"));
useEffect(() => { useEffect(() => {
getAdminModules(); dispatch(getAdminModules());
}, [getAdminModules]); }, [dispatch]);
return <Modules modules={modules} isLoading={loading} />; return <Modules modules={modules} isLoading={loading} />;
}; };
AdminModulesConnected.propTypes = {
modules: PropTypes.object,
loading: PropTypes.bool,
getAdminModules: PropTypes.func.isRequired,
};
const mapStateToProps = (state) => ({
loading: state.adminStore.get("fetchingModules"),
modules: state.adminStore.get("modules"),
});
export const AdminModules = connect(mapStateToProps, { getAdminModules })(
AdminModulesConnected
);

View File

@ -1,15 +1,17 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { Stat } from "./stat"; import { Stat } from "./stat";
import { getStats } from "../../actions/admins"; import { getStats } from "../../actions/admins";
const StatsConnected = ({ stats, getStats }) => { export const Stats = () => {
const dispatch = useDispatch();
const stats = useSelector((state) => state.adminStore.get("stats"));
useEffect(() => { useEffect(() => {
getStats(); dispatch(getStats());
}, [getStats]); }, [dispatch]);
return ( return (
<div className="row d-flex flex-wrap"> <div className="row d-flex flex-wrap">
@ -34,13 +36,3 @@ const StatsConnected = ({ stats, getStats }) => {
</div> </div>
); );
}; };
StatsConnected.propTypes = {
stats: PropTypes.object,
getStats: PropTypes.func,
};
const mapStateToProps = (state) => ({
stats: state.adminStore.get("stats"),
});
export const Stats = connect(mapStateToProps, { getStats })(StatsConnected);

View File

@ -1,7 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { List } from "immutable";
import { updateUser, deleteUser } from "../../actions/admins"; import { updateUser, deleteUser } from "../../actions/admins";
@ -11,7 +10,7 @@ import { PolochonSelect } from "../polochons/select";
import { FormModal } from "../forms/modal"; import { FormModal } from "../forms/modal";
import { FormInput } from "../forms/input"; import { FormInput } from "../forms/input";
const UserEditConnect = ({ export const UserEdit = ({
id, id,
name, name,
admin: initAdmin, admin: initAdmin,
@ -19,10 +18,10 @@ const UserEditConnect = ({
polochonToken, polochonToken,
polochonId: initPolochonId, polochonId: initPolochonId,
polochonActivated: initPolochonActivated, polochonActivated: initPolochonActivated,
updateUser,
deleteUser,
publicPolochons,
}) => { }) => {
const dispatch = useDispatch();
const publicPolochons = useSelector((state) => state.polochon.get("public"));
const [modal, setModal] = useState(false); const [modal, setModal] = useState(false);
const [admin, setAdmin] = useState(initAdmin); const [admin, setAdmin] = useState(initAdmin);
const [activated, setActivated] = useState(initActivated); const [activated, setActivated] = useState(initActivated);
@ -38,6 +37,7 @@ const UserEditConnect = ({
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
dispatch(
updateUser({ updateUser({
userId: id, userId: id,
polochonToken: token, polochonToken: token,
@ -46,7 +46,8 @@ const UserEditConnect = ({
polochonId, polochonId,
polochonActivated, polochonActivated,
password, password,
}); })
);
setModal(false); setModal(false);
}; };
@ -55,7 +56,7 @@ const UserEditConnect = ({
e.preventDefault(); e.preventDefault();
} }
if (confirmDelete) { if (confirmDelete) {
deleteUser(name); dispatch(deleteUser(name));
setModal(false); setModal(false);
} else { } else {
setConfirmDelete(true); setConfirmDelete(true);
@ -137,23 +138,12 @@ const UserEditConnect = ({
</span> </span>
); );
}; };
UserEditConnect.propTypes = { UserEdit.propTypes = {
id: PropTypes.string, id: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
activated: PropTypes.bool, activated: PropTypes.bool,
admin: PropTypes.bool, admin: PropTypes.bool,
updateUser: PropTypes.func,
deleteUser: PropTypes.func,
polochonToken: PropTypes.string, polochonToken: PropTypes.string,
polochonId: PropTypes.string, polochonId: PropTypes.string,
polochonActivated: PropTypes.bool, polochonActivated: PropTypes.bool,
publicPolochons: PropTypes.instanceOf(List),
}; };
const mapStateToProps = (state) => ({
publicPolochons: state.polochon.get("public"),
});
export const UserEdit = connect(mapStateToProps, { updateUser, deleteUser })(
UserEditConnect
);

View File

@ -1,23 +1,19 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { List } from "immutable";
import { connect } from "react-redux";
import { User } from "./user"; import { User } from "./user";
import { getUsers } from "../../actions/admins"; import { getUsers } from "../../actions/admins";
import { getPolochons } from "../../actions/polochon"; import { getPolochons } from "../../actions/polochon";
const mapStateToProps = (state) => ({ export const UserList = () => {
users: state.adminStore.get("users"), const dispatch = useDispatch();
}); const users = useSelector((state) => state.adminStore.get("users"));
const mapDispatchToProps = { getUsers, getPolochons };
const UserListConnect = ({ users, getUsers, getPolochons }) => {
useEffect(() => { useEffect(() => {
getUsers(); dispatch(getUsers());
getPolochons(); dispatch(getPolochons());
}, [getUsers, getPolochons]); }, [dispatch]);
return ( return (
<div className="table-responsive my-2"> <div className="table-responsive my-2">
@ -54,13 +50,3 @@ const UserListConnect = ({ users, getUsers, getPolochons }) => {
</div> </div>
); );
}; };
UserListConnect.propTypes = {
getUsers: PropTypes.func,
getPolochons: PropTypes.func,
users: PropTypes.PropTypes.instanceOf(List),
};
export const UserList = connect(
mapStateToProps,
mapDispatchToProps
)(UserListConnect);

View File

@ -1,35 +1,27 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import SweetAlert from "react-bootstrap-sweetalert"; import SweetAlert from "react-bootstrap-sweetalert";
import { connect } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { dismissAlert } from "../../actions/alerts"; import { dismissAlert } from "../../actions/alerts";
const mapStateToProps = (state) => ({ const Alert = () => {
show: state.alerts.get("show"), const dispatch = useDispatch();
title: state.alerts.get("message"),
type: state.alerts.get("type"),
});
const mapDispatchToProps = { dismissAlert };
const Alert = (props) => { const show = useSelector((state) => state.alerts.get("show"));
if (!props.show) { const title = useSelector((state) => state.alerts.get("message"));
const type = useSelector((state) => state.alerts.get("type"));
if (!show) {
return null; return null;
} }
return ( return (
<SweetAlert <SweetAlert
type={props.type} type={type}
title={props.title} title={title}
onConfirm={props.dismissAlert} onConfirm={() => dispatch(dismissAlert())}
/> />
); );
}; };
Alert.propTypes = {
show: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
dismissAlert: PropTypes.func.isRequired,
type: PropTypes.string,
};
export default connect(mapStateToProps, mapDispatchToProps)(Alert); export default Alert;

View File

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { prettySize } from "../../utils"; import { prettySize } from "../../utils";
import { addTorrent } from "../../actions/torrents"; import { addTorrent } from "../../actions/torrents";
@ -45,10 +45,10 @@ function buildMenuItems(torrents) {
return entries; return entries;
} }
const torrentsButton = ({ torrents, search, searching, addTorrent, url }) => { export const TorrentsButton = ({ torrents, search, searching, url }) => {
/* eslint-disable */ const dispatch = useDispatch();
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
/* eslint-enable */
const entries = buildMenuItems(torrents); const entries = buildMenuItems(torrents);
const count = torrents && torrents.size !== 0 ? torrents.size : 0; const count = torrents && torrents.size !== 0 ? torrents.size : 0;
@ -97,7 +97,10 @@ const torrentsButton = ({ torrents, search, searching, addTorrent, url }) => {
return <Dropdown.Divider key={index} />; return <Dropdown.Divider key={index} />;
case "entry": case "entry":
return ( return (
<Dropdown.Item key={index} onClick={() => addTorrent(e.url)}> <Dropdown.Item
key={index}
onClick={() => dispatch(addTorrent(e.url))}
>
{e.quality} {e.quality}
{e.size !== 0 && ( {e.size !== 0 && (
<small className="ml-1">({prettySize(e.size)})</small> <small className="ml-1">({prettySize(e.size)})</small>
@ -111,15 +114,12 @@ const torrentsButton = ({ torrents, search, searching, addTorrent, url }) => {
</span> </span>
); );
}; };
torrentsButton.propTypes = { TorrentsButton.propTypes = {
torrents: PropTypes.instanceOf(List), torrents: PropTypes.instanceOf(List),
searching: PropTypes.bool, searching: PropTypes.bool,
search: PropTypes.func.isRequired, search: PropTypes.func.isRequired,
addTorrent: PropTypes.func.isRequired,
url: PropTypes.string, url: PropTypes.string,
}; };
torrentsButton.defaultProps = { TorrentsButton.defaultProps = {
torrents: List(), torrents: List(),
}; };
export const TorrentsButton = connect(null, { addTorrent })(torrentsButton);

View File

@ -1,10 +1,20 @@
import React from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import { Form, FormGroup, FormControl, FormLabel } from "react-bootstrap"; import { Form, FormGroup, FormControl, FormLabel } from "react-bootstrap";
const ExplorerOptions = ({ display, params, options, type, history }) => { const ExplorerOptions = ({
// Should this componennt be displayed display,
params,
options,
type,
history,
fetch,
}) => {
useEffect(() => {
fetch();
}, [fetch]);
if (!display) { if (!display) {
return null; return null;
} }
@ -103,6 +113,7 @@ ExplorerOptions.propTypes = {
type: PropTypes.string, type: PropTypes.string,
options: PropTypes.object, options: PropTypes.object,
display: PropTypes.bool, display: PropTypes.bool,
fetch: PropTypes.func,
}; };
export default withRouter(ExplorerOptions); export default withRouter(ExplorerOptions);

View File

@ -55,6 +55,7 @@ const ListPosters = (props) => {
type={props.type} type={props.type}
display={displayExplorerOptions} display={displayExplorerOptions}
params={props.params} params={props.params}
fetch={props.exploreFetchOptions}
options={props.exploreOptions} options={props.exploreOptions}
/> />
<Posters <Posters
@ -81,6 +82,7 @@ ListPosters.propTypes = {
placeHolder: PropTypes.string.isRequired, placeHolder: PropTypes.string.isRequired,
updateFilter: PropTypes.func.isRequired, updateFilter: PropTypes.func.isRequired,
filter: PropTypes.string, filter: PropTypes.string,
exploreFetchOptions: PropTypes.func,
}; };
export default ListPosters; export default ListPosters;

View File

@ -1,11 +1,14 @@
import React from "react"; import React, { useEffect, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { OrderedMap, Map } from "immutable"; import { useSelector, useDispatch } from "react-redux";
import { connect } from "react-redux"; import { Map } from "immutable";
import { import {
selectMovie, selectMovie,
updateFilter, updateFilter,
movieWishlistToggle, movieWishlistToggle,
fetchMovies,
getMovieExploreOptions,
} from "../../actions/movies"; } from "../../actions/movies";
import ListDetails from "../list/details"; import ListDetails from "../list/details";
@ -18,55 +21,87 @@ import { ShowMore } from "../buttons/showMore";
import { MovieSubtitlesButton } from "./subtitlesButton"; import { MovieSubtitlesButton } from "./subtitlesButton";
import { MovieTorrentsButton } from "./torrentsButton"; import { MovieTorrentsButton } from "./torrentsButton";
function mapStateToProps(state) { const fetchUrl = (match) => {
return { switch (match.path) {
loading: state.movieStore.get("loading"), case "/movies/polochon":
movies: state.movieStore.get("movies"), return "/movies/polochon";
filter: state.movieStore.get("filter"), case "/movies/wishlist":
selectedImdbId: state.movieStore.get("selectedImdbId"), return "/wishlist/movies";
exploreOptions: state.movieStore.get("exploreOptions"), case "/movies/search/:search":
}; return "/movies/search/" + match.params.search;
} case "/movies/explore/:source/:category":
const mapDispatchToProps = { return (
selectMovie, "/movies/explore?source=" +
updateFilter, encodeURI(match.params.source) +
movieWishlistToggle, "&category=" +
encodeURI(match.params.category)
);
default:
return null;
}
}; };
const MovieList = (props) => { const MovieList = ({ match }) => {
let selectedMovie = Map(); const dispatch = useDispatch();
if (props.movies !== undefined && props.movies.has(props.selectedImdbId)) {
selectedMovie = props.movies.get(props.selectedImdbId); useEffect(() => {
const url = fetchUrl(match);
if (url !== null) {
dispatch(fetchMovies(url));
} }
}, [dispatch, match]);
const loading = useSelector((state) => state.movieStore.get("loading"));
const movies = useSelector((state) => state.movieStore.get("movies"));
const filter = useSelector((state) => state.movieStore.get("filter"));
const selectedImdbId = useSelector((state) =>
state.movieStore.get("selectedImdbId")
);
const exploreOptions = useSelector((state) =>
state.movieStore.get("exploreOptions")
);
let selectedMovie = Map();
if (movies !== undefined && movies.has(selectedImdbId)) {
selectedMovie = movies.get(selectedImdbId);
}
const selectFunc = useCallback((id) => dispatch(selectMovie(id)), [dispatch]);
const filterFunc = useCallback((filter) => dispatch(updateFilter(filter)), [
dispatch,
]);
const exploreFetchOptions = useCallback(
() => dispatch(getMovieExploreOptions()),
[dispatch]
);
return ( return (
<div className="row"> <div className="row">
<ListPosters <ListPosters
data={props.movies} data={movies}
type="movies" type="movies"
placeHolder="Filter movies..." placeHolder="Filter movies..."
exploreOptions={props.exploreOptions} exploreFetchOptions={exploreFetchOptions}
selectedImdbId={props.selectedImdbId} exploreOptions={exploreOptions}
updateFilter={props.updateFilter} selectedImdbId={selectedImdbId}
filter={props.filter} updateFilter={filterFunc}
onClick={props.selectMovie} filter={filter}
onDoubleClick={function () { onClick={selectFunc}
return; onDoubleClick={() => {}}
}} onKeyEnter={() => {}}
onKeyEnter={function () { params={match.params}
return; loading={loading}
}}
params={props.match.params}
loading={props.loading}
/> />
<ListDetails <ListDetails
data={selectedMovie} data={selectedMovie}
loading={props.loading} loading={loading}
wishlist={() => wishlist={() =>
props.movieWishlistToggle( dispatch(
movieWishlistToggle(
selectedMovie.get("imdb_id"), selectedMovie.get("imdb_id"),
isWishlisted(selectedMovie) isWishlisted(selectedMovie)
) )
)
} }
> >
<ShowMore <ShowMore
@ -91,15 +126,12 @@ const MovieList = (props) => {
); );
}; };
MovieList.propTypes = { MovieList.propTypes = {
movies: PropTypes.instanceOf(OrderedMap), fetchMovies: PropTypes.func,
exploreOptions: PropTypes.instanceOf(Map), getMovieExploreOptions: PropTypes.func,
selectedImdbId: PropTypes.string,
filter: PropTypes.string,
loading: PropTypes.bool,
updateFilter: PropTypes.func, updateFilter: PropTypes.func,
movieWishlistToggle: PropTypes.func, movieWishlistToggle: PropTypes.func,
selectMovie: PropTypes.func, selectMovie: PropTypes.func,
match: PropTypes.object, match: PropTypes.object,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(MovieList); export default MovieList;

View File

@ -1,65 +0,0 @@
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.object,
match: PropTypes.object,
isExplorerFetched: PropTypes.bool.isRequired,
fetchMovies: PropTypes.func.isRequired,
getMovieExploreOptions: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(MoviesRoute);

View File

@ -1,35 +1,32 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { searchMovieSubtitles } from "../../actions/subtitles"; import { searchMovieSubtitles } from "../../actions/subtitles";
import { SubtitlesButton } from "../buttons/subtitles"; import { SubtitlesButton } from "../buttons/subtitles";
const movieSubtitlesButton = ({ export const MovieSubtitlesButton = ({
inLibrary, inLibrary,
imdbId, imdbId,
searching, searching,
searchMovieSubtitles,
subtitles, subtitles,
}) => ( }) => {
const dispatch = useDispatch();
return (
<SubtitlesButton <SubtitlesButton
inLibrary={inLibrary} inLibrary={inLibrary}
searching={searching} searching={searching}
subtitles={subtitles} subtitles={subtitles}
search={() => searchMovieSubtitles(imdbId)} search={() => dispatch(searchMovieSubtitles(imdbId))}
/> />
); );
};
movieSubtitlesButton.propTypes = { MovieSubtitlesButton.propTypes = {
searching: PropTypes.bool, searching: PropTypes.bool,
inLibrary: PropTypes.bool, inLibrary: PropTypes.bool,
imdbId: PropTypes.string, imdbId: PropTypes.string,
searchMovieSubtitles: PropTypes.func,
subtitles: PropTypes.instanceOf(List), subtitles: PropTypes.instanceOf(List),
}; };
export const MovieSubtitlesButton = connect(null, { searchMovieSubtitles })(
movieSubtitlesButton
);

View File

@ -1,33 +1,26 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { getMovieDetails } from "../../actions/movies"; import { getMovieDetails } from "../../actions/movies";
import { TorrentsButton } from "../buttons/torrents"; import { TorrentsButton } from "../buttons/torrents";
const movieTorrentsButton = ({ export const MovieTorrentsButton = ({ torrents, imdbId, title, searching }) => {
torrents, const dispatch = useDispatch();
imdbId, return (
title,
searching,
getMovieDetails,
}) => (
<TorrentsButton <TorrentsButton
torrents={torrents} torrents={torrents}
searching={searching} searching={searching}
search={() => getMovieDetails(imdbId)} search={() => dispatch(getMovieDetails(imdbId))}
url={`#/torrents/search/movies/${encodeURI(title)}`} url={`#/torrents/search/movies/${encodeURI(title)}`}
/> />
); );
movieTorrentsButton.propTypes = { };
MovieTorrentsButton.propTypes = {
torrents: PropTypes.instanceOf(List), torrents: PropTypes.instanceOf(List),
imdbId: PropTypes.string, imdbId: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
searching: PropTypes.bool, searching: PropTypes.bool,
getMovieDetails: PropTypes.func, getMovieDetails: PropTypes.func,
}; };
export const MovieTorrentsButton = connect(null, { getMovieDetails })(
movieTorrentsButton
);

View File

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import { connect } from "react-redux"; import { useSelector } from "react-redux";
import { LinkContainer } from "react-router-bootstrap"; import { LinkContainer } from "react-router-bootstrap";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
@ -8,24 +8,15 @@ import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar"; import Navbar from "react-bootstrap/Navbar";
import NavDropdown from "react-bootstrap/NavDropdown"; import NavDropdown from "react-bootstrap/NavDropdown";
const mapStateToProps = (state) => { const AppNavBar = () => {
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,
};
};
const AppNavBar = (props) => {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const username = useSelector((state) => state.userStore.get("username"));
const isAdmin = useSelector((state) => state.userStore.get("isAdmin"));
const torrentCount = useSelector(
(state) => state.torrentStore.get("torrents").size
);
return ( return (
<Navbar <Navbar
fixed="top" fixed="top"
@ -45,7 +36,7 @@ const AppNavBar = (props) => {
<MoviesDropdown /> <MoviesDropdown />
<ShowsDropdown /> <ShowsDropdown />
<WishlistDropdown /> <WishlistDropdown />
<TorrentsDropdown torrentsCount={props.torrentCount} /> <TorrentsDropdown torrentsCount={torrentCount} />
</Nav> </Nav>
<Nav> <Nav>
<Route <Route
@ -70,20 +61,16 @@ const AppNavBar = (props) => {
/> />
)} )}
/> />
<UserDropdown username={props.username} isAdmin={props.isAdmin} /> <UserDropdown username={username} isAdmin={isAdmin} />
</Nav> </Nav>
</Navbar.Collapse> </Navbar.Collapse>
</Navbar> </Navbar>
); );
}; };
AppNavBar.propTypes = { AppNavBar.propTypes = {
torrentCount: PropTypes.number.isRequired,
username: PropTypes.string.isRequired,
isAdmin: PropTypes.bool.isRequired,
history: PropTypes.object, history: PropTypes.object,
}; };
export default AppNavBar;
export default connect(mapStateToProps)(AppNavBar);
const Search = ({ path, placeholder, setExpanded, history }) => { const Search = ({ path, placeholder, setExpanded, history }) => {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");

View File

@ -1,12 +1,12 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { Toast } from "react-bootstrap"; import { Toast } from "react-bootstrap";
import { removeNotification } from "../../actions/notifications"; import { removeNotification } from "../../actions/notifications";
const NotificationConnected = ({ export const Notification = ({
id, id,
icon, icon,
title, title,
@ -14,13 +14,13 @@ const NotificationConnected = ({
imageUrl, imageUrl,
autohide, autohide,
delay, delay,
removeNotification,
}) => { }) => {
const dispatch = useDispatch();
const [show, setShow] = useState(true); const [show, setShow] = useState(true);
const hide = () => { const hide = () => {
setShow(false); setShow(false);
setTimeout(() => removeNotification(id), 200); setTimeout(() => dispatch(removeNotification(id)), 200);
}; };
return ( return (
@ -38,7 +38,7 @@ const NotificationConnected = ({
</Toast> </Toast>
); );
}; };
NotificationConnected.propTypes = { Notification.propTypes = {
id: PropTypes.string, id: PropTypes.string,
icon: PropTypes.string, icon: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
@ -46,10 +46,9 @@ NotificationConnected.propTypes = {
imageUrl: PropTypes.string, imageUrl: PropTypes.string,
autohide: PropTypes.bool, autohide: PropTypes.bool,
delay: PropTypes.number, delay: PropTypes.number,
removeNotification: PropTypes.func,
}; };
NotificationConnected.defaultProps = { Notification.defaultProps = {
autohide: false, autohide: false,
delay: 5000, delay: 5000,
icon: "", icon: "",
@ -57,7 +56,3 @@ NotificationConnected.defaultProps = {
title: "Info", title: "Info",
message: "", message: "",
}; };
export const Notification = connect(null, { removeNotification })(
NotificationConnected
);

View File

@ -1,11 +1,12 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useSelector } from "react-redux";
import { Notification } from "./notification"; import { Notification } from "./notification";
const NotificationsConnected = ({ notifications }) => { export const Notifications = () => {
const notifications = useSelector((state) => state.notifications);
return ( return (
<div className="notifications"> <div className="notifications">
{notifications.map((el) => ( {notifications.map((el) => (
@ -23,12 +24,6 @@ const NotificationsConnected = ({ notifications }) => {
</div> </div>
); );
}; };
NotificationsConnected.propTypes = { Notifications.propTypes = {
notifications: PropTypes.instanceOf(List), notifications: PropTypes.instanceOf(List),
}; };
const mapStateToProps = (state) => ({
notifications: state.notifications,
});
export const Notifications = connect(mapStateToProps)(NotificationsConnected);

View File

@ -1,12 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { addPolochon } from "../../actions/polochon"; import { addPolochon } from "../../actions/polochon";
import { PolochonEdit } from "./edit"; import { PolochonEdit } from "./edit";
export const PolochonAddConnected = ({ addPolochon }) => { export const PolochonAdd = () => {
const dispatch = useDispatch();
const [modal, setModal] = useState(false); const [modal, setModal] = useState(false);
return ( return (
@ -21,13 +22,11 @@ export const PolochonAddConnected = ({ addPolochon }) => {
icon="plus" icon="plus"
show={modal} show={modal}
setShow={setModal} setShow={setModal}
update={addPolochon} update={(params) => dispatch(addPolochon(params))}
/> />
</React.Fragment> </React.Fragment>
); );
}; };
PolochonAddConnected.propTypes = { PolochonAdd.propTypes = {
addPolochon: PropTypes.func, addPolochon: PropTypes.func,
}; };
export const PolochonAdd = connect(null, { addPolochon })(PolochonAddConnected);

View File

@ -1,25 +1,18 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import { useSelector, useDispatch } from "react-redux";
import { connect } from "react-redux";
import { List } from "immutable";
import { getManagedPolochons } from "../../actions/polochon"; import { getManagedPolochons } from "../../actions/polochon";
import { Polochon } from "./polochon"; import { Polochon } from "./polochon";
import { PolochonAdd } from "./add"; import { PolochonAdd } from "./add";
const mapStateToProps = (state) => ({ export const PolochonList = () => {
managedList: state.polochon.get("managed"), const list = useSelector((state) => state.polochon.get("managed"));
}); const dispatch = useDispatch();
const mapDispatchToProps = {
getManagedPolochons,
};
const PolochonListConnected = ({ getManagedPolochons, managedList }) => {
useEffect(() => { useEffect(() => {
getManagedPolochons(); dispatch(getManagedPolochons());
}, [getManagedPolochons]); }, [dispatch]);
return ( return (
<div className="row mb-3"> <div className="row mb-3">
@ -27,7 +20,7 @@ const PolochonListConnected = ({ getManagedPolochons, managedList }) => {
<h2>My polochons</h2> <h2>My polochons</h2>
<hr /> <hr />
<span> <span>
{managedList.map((el, index) => ( {list.map((el, index) => (
<Polochon <Polochon
key={index} key={index}
id={el.get("id")} id={el.get("id")}
@ -44,12 +37,3 @@ const PolochonListConnected = ({ getManagedPolochons, managedList }) => {
</div> </div>
); );
}; };
PolochonListConnected.propTypes = {
getManagedPolochons: PropTypes.func,
managedList: PropTypes.instanceOf(List),
};
export const PolochonList = connect(
mapStateToProps,
mapDispatchToProps
)(PolochonListConnected);

View File

@ -1,25 +1,18 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { PolochonUsers } from "./users"; import { PolochonUsers } from "./users";
import { PolochonEdit } from "./edit"; import { PolochonEdit } from "./edit";
import { updatePolochon, deletePolochon } from "../../actions/polochon"; import { updatePolochon, deletePolochon } from "../../actions/polochon";
export const PolochonConnected = ({ export const Polochon = ({ id, name, token, url, authToken, users }) => {
id,
name,
token,
url,
authToken,
users,
updatePolochon,
deletePolochon,
}) => {
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
const dispatch = useDispatch();
return ( return (
<React.Fragment> <React.Fragment>
<div className="card mb-2"> <div className="card mb-2">
@ -33,7 +26,7 @@ export const PolochonConnected = ({
/> />
<i <i
className="fa fa-trash clickable" className="fa fa-trash clickable"
onClick={() => deletePolochon(id)} onClick={() => dispatch(deletePolochon(id))}
/> />
</span> </span>
</div> </div>
@ -49,7 +42,7 @@ export const PolochonConnected = ({
title="Polochon config" title="Polochon config"
show={edit} show={edit}
setShow={setEdit} setShow={setEdit}
update={updatePolochon} update={(params) => dispatch(updatePolochon(params))}
id={id} id={id}
initialName={name} initialName={name}
initialUrl={url} initialUrl={url}
@ -58,17 +51,11 @@ export const PolochonConnected = ({
</React.Fragment> </React.Fragment>
); );
}; };
PolochonConnected.propTypes = { Polochon.propTypes = {
id: PropTypes.string, id: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
token: PropTypes.string, token: PropTypes.string,
url: PropTypes.string, url: PropTypes.string,
authToken: PropTypes.string, authToken: PropTypes.string,
users: PropTypes.instanceOf(List), users: PropTypes.instanceOf(List),
updatePolochon: PropTypes.func,
deletePolochon: PropTypes.func,
}; };
export const Polochon = connect(null, { updatePolochon, deletePolochon })(
PolochonConnected
);

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { editPolochonUser } from "../../actions/polochon"; import { editPolochonUser } from "../../actions/polochon";
@ -9,14 +9,14 @@ import Toggle from "react-bootstrap-toggle";
import { FormModal } from "../forms/modal"; import { FormModal } from "../forms/modal";
import { FormInput } from "../forms/input"; import { FormInput } from "../forms/input";
export const PolochonUserConnected = ({ export const PolochonUser = ({
polochonId, polochonId,
id, id,
name, name,
initialToken, initialToken,
initialActivated, initialActivated,
editPolochonUser,
}) => { }) => {
const dispatch = useDispatch();
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
const [token, setToken] = useState(initialToken); const [token, setToken] = useState(initialToken);
const [activated, setActivated] = useState(initialActivated); const [activated, setActivated] = useState(initialActivated);
@ -27,12 +27,14 @@ export const PolochonUserConnected = ({
}, [initialActivated, initialToken]); }, [initialActivated, initialToken]);
const handleSubmit = () => { const handleSubmit = () => {
dispatch(
editPolochonUser({ editPolochonUser({
polochonId, polochonId,
id, id,
token, token,
activated, activated,
}); })
);
setEdit(false); setEdit(false);
}; };
@ -67,7 +69,7 @@ export const PolochonUserConnected = ({
</tr> </tr>
); );
}; };
PolochonUserConnected.propTypes = { PolochonUser.propTypes = {
polochonId: PropTypes.string, polochonId: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
@ -75,7 +77,3 @@ PolochonUserConnected.propTypes = {
initialActivated: PropTypes.bool, initialActivated: PropTypes.bool,
editPolochonUser: PropTypes.func, editPolochonUser: PropTypes.func,
}; };
export const PolochonUser = connect(null, { editPolochonUser })(
PolochonUserConnected
);

View File

@ -1,7 +1,6 @@
import React from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Map } from "immutable"; import { useSelector, useDispatch } from "react-redux";
import { connect } from "react-redux";
import Loader from "../loader/loader"; import Loader from "../loader/loader";
@ -9,12 +8,17 @@ import { Fanart } from "./details/fanart";
import { Header } from "./details/header"; import { Header } from "./details/header";
import { SeasonsList } from "./details/seasons"; import { SeasonsList } from "./details/seasons";
const mapStateToProps = (state) => ({ import { fetchShowDetails } from "../../actions/shows";
loading: state.showStore.get("loading"),
show: state.showStore.get("show"), export const ShowDetails = ({ match }) => {
}); const dispatch = useDispatch();
const loading = useSelector((state) => state.showStore.get("loading"));
const show = useSelector((state) => state.showStore.get("show"));
useEffect(() => {
dispatch(fetchShowDetails(match.params.imdbId));
}, [dispatch, match]);
const showDetails = ({ show, loading }) => {
if (loading === true) { if (loading === true) {
return <Loader />; return <Loader />;
} }
@ -29,8 +33,6 @@ const showDetails = ({ show, loading }) => {
</React.Fragment> </React.Fragment>
); );
}; };
showDetails.propTypes = { ShowDetails.propTypes = {
loading: PropTypes.bool, match: PropTypes.object.isRequired,
show: PropTypes.instanceOf(Map),
}; };
export const ShowDetails = connect(mapStateToProps)(showDetails);

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Map } from "immutable"; import { Map } from "immutable";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { showWishlistToggle } from "../../../actions/shows"; import { showWishlistToggle } from "../../../actions/shows";
@ -24,12 +24,17 @@ import { EpisodeSubtitlesButton } from "./subtitlesButton";
import { EpisodeThumb } from "./episodeThumb"; import { EpisodeThumb } from "./episodeThumb";
import { EpisodeTorrentsButton } from "./torrentsButton"; import { EpisodeTorrentsButton } from "./torrentsButton";
const mapStateToProps = (state) => ({ export const Episode = (props) => {
trackedSeason: state.showStore.getIn(["show", "tracked_season"], null), const dispatch = useDispatch();
trackedEpisode: state.showStore.getIn(["show", "tracked_episode"], null),
});
const episode = (props) => ( const trackedSeason = useSelector((state) =>
state.showStore.getIn(["show", "tracked_season"], null)
);
const trackedEpisode = useSelector((state) =>
state.showStore.getIn(["show", "tracked_episode"], null)
);
return (
<div className="d-flex flex-column flex-lg-row mb-3 pb-3 border-bottom border-light"> <div className="d-flex flex-column flex-lg-row mb-3 pb-3 border-bottom border-light">
<EpisodeThumb url={props.data.get("thumb")} /> <EpisodeThumb url={props.data.get("thumb")} />
<div className="d-flex flex-column"> <div className="d-flex flex-column">
@ -37,16 +42,18 @@ const episode = (props) => (
title={`${props.data.get("episode")}. ${props.data.get("title")}`} title={`${props.data.get("episode")}. ${props.data.get("title")}`}
wishlisted={isEpisodeWishlisted( wishlisted={isEpisodeWishlisted(
props.data, props.data,
props.trackedSeason, trackedSeason,
props.trackedEpisode trackedEpisode
)} )}
wishlist={() => wishlist={() =>
props.showWishlistToggle( dispatch(
showWishlistToggle(
isEpisodeWishlisted(props.data), isEpisodeWishlisted(props.data),
props.data.get("show_imdb_id"), props.data.get("show_imdb_id"),
props.data.get("season"), props.data.get("season"),
props.data.get("episode") props.data.get("episode")
) )
)
} }
/> />
<ReleaseDate date={props.data.get("aired")} /> <ReleaseDate date={props.data.get("aired")} />
@ -96,15 +103,9 @@ const episode = (props) => (
</ShowMore> </ShowMore>
</div> </div>
</div> </div>
); );
episode.propTypes = { };
data: PropTypes.instanceOf(Map).isRequired, Episode.propTypes = {
trackedSeason: PropTypes.number, data: PropTypes.instanceOf(Map).isRequired,
trackedEpisode: PropTypes.number, showName: PropTypes.string.isRequired,
showName: PropTypes.string.isRequired,
showWishlistToggle: PropTypes.func,
}; };
export const Episode = connect(mapStateToProps, { showWishlistToggle })(
episode
);

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Map } from "immutable"; import { Map } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { isWishlisted } from "../../../utils"; import { isWishlisted } from "../../../utils";
@ -15,7 +15,9 @@ import { TrackingLabel } from "../../details/tracking";
import { ImdbBadge } from "../../buttons/imdb"; import { ImdbBadge } from "../../buttons/imdb";
export const header = (props) => ( export const Header = (props) => {
const dispatch = useDispatch();
return (
<div className="card col-12 col-md-10 offset-md-1 mt-n3 mb-3"> <div className="card col-12 col-md-10 offset-md-1 mt-n3 mb-3">
<div className="d-flex flex-column flex-md-row"> <div className="d-flex flex-column flex-md-row">
<div className="d-flex justify-content-center"> <div className="d-flex justify-content-center">
@ -31,10 +33,12 @@ export const header = (props) => (
title={props.data.get("title")} title={props.data.get("title")}
wishlisted={isWishlisted(props.data)} wishlisted={isWishlisted(props.data)}
wishlist={() => wishlist={() =>
props.showWishlistToggle( dispatch(
showWishlistToggle(
isWishlisted(props.data), isWishlisted(props.data),
props.data.get("imdb_id") props.data.get("imdb_id")
) )
)
} }
/> />
</p> </p>
@ -61,10 +65,8 @@ export const header = (props) => (
</div> </div>
</div> </div>
</div> </div>
); );
header.propTypes = { };
data: PropTypes.instanceOf(Map), Header.propTypes = {
showWishlistToggle: PropTypes.func, data: PropTypes.instanceOf(Map),
}; };
export const Header = connect(null, { showWishlistToggle })(header);

View File

@ -1,39 +1,37 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "immutable"; import { List } from "immutable";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { searchEpisodeSubtitles } from "../../../actions/subtitles"; import { searchEpisodeSubtitles } from "../../../actions/subtitles";
import { SubtitlesButton } from "../../buttons/subtitles"; import { SubtitlesButton } from "../../buttons/subtitles";
const episodeSubtitlesButton = ({ export const EpisodeSubtitlesButton = ({
inLibrary, inLibrary,
imdbId, imdbId,
season, season,
episode, episode,
searching, searching,
searchEpisodeSubtitles,
subtitles, subtitles,
}) => ( }) => {
const dispatch = useDispatch();
return (
<SubtitlesButton <SubtitlesButton
subtitles={subtitles} subtitles={subtitles}
inLibrary={inLibrary} inLibrary={inLibrary}
searching={searching} searching={searching}
search={() => searchEpisodeSubtitles(imdbId, season, episode)} search={() => dispatch(searchEpisodeSubtitles(imdbId, season, episode))}
/> />
); );
};
episodeSubtitlesButton.propTypes = { EpisodeSubtitlesButton.propTypes = {
inLibrary: PropTypes.bool, inLibrary: PropTypes.bool,
searching: PropTypes.bool, searching: PropTypes.bool,
imdbId: PropTypes.string, imdbId: PropTypes.string,
season: PropTypes.number, season: PropTypes.number,
episode: PropTypes.number, episode: PropTypes.number,
searchEpisodeSubtitles: PropTypes.func,
subtitles: PropTypes.instanceOf(List), subtitles: PropTypes.instanceOf(List),
}; };
export const EpisodeSubtitlesButton = connect(null, { searchEpisodeSubtitles })(
episodeSubtitlesButton
);

View File

@ -1,40 +1,39 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { List } from "immutable"; import { List } from "immutable";
import { getEpisodeDetails } from "../../../actions/shows"; import { getEpisodeDetails } from "../../../actions/shows";
import { TorrentsButton } from "../../buttons/torrents"; import { TorrentsButton } from "../../buttons/torrents";
import { prettyEpisodeName } from "../../../utils"; import { prettyEpisodeName } from "../../../utils";
const episodeTorrentsButton = ({ export const EpisodeTorrentsButton = ({
torrents, torrents,
imdbId, imdbId,
season, season,
episode, episode,
showName, showName,
searching, searching,
getEpisodeDetails, }) => {
}) => ( const dispatch = useDispatch();
return (
<TorrentsButton <TorrentsButton
torrents={torrents} torrents={torrents}
searching={searching} searching={searching}
search={() => getEpisodeDetails(imdbId, season, episode)} search={() => dispatch(getEpisodeDetails(imdbId, season, episode))}
url={`#/torrents/search/shows/${encodeURI( url={`#/torrents/search/shows/${encodeURI(
prettyEpisodeName(showName, season, episode) prettyEpisodeName(showName, season, episode)
)}`} )}`}
/> />
); );
episodeTorrentsButton.propTypes = { };
EpisodeTorrentsButton.propTypes = {
torrents: PropTypes.instanceOf(List), torrents: PropTypes.instanceOf(List),
showName: PropTypes.string.isRequired, showName: PropTypes.string.isRequired,
imdbId: PropTypes.string.isRequired, imdbId: PropTypes.string.isRequired,
episode: PropTypes.number.isRequired, episode: PropTypes.number.isRequired,
season: PropTypes.number.isRequired, season: PropTypes.number.isRequired,
searching: PropTypes.bool.isRequired, searching: PropTypes.bool.isRequired,
getEpisodeDetails: PropTypes.func.isRequired,
}; };
export const EpisodeTorrentsButton = connect(null, { getEpisodeDetails })(
episodeTorrentsButton
);

View File

@ -1,12 +1,14 @@
import React from "react"; import React, { useEffect, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Map } from "immutable"; import { Map } from "immutable";
import { connect } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { import {
fetchShows,
selectShow, selectShow,
showWishlistToggle, showWishlistToggle,
getShowDetails,
updateFilter, updateFilter,
getShowExploreOptions,
} from "../../actions/shows"; } from "../../actions/shows";
import { isWishlisted } from "../../utils"; import { isWishlisted } from "../../utils";
@ -14,56 +16,91 @@ import { isWishlisted } from "../../utils";
import ListDetails from "../list/details"; import ListDetails from "../list/details";
import ListPosters from "../list/posters"; import ListPosters from "../list/posters";
function mapStateToProps(state) { const fetchUrl = (match) => {
return { switch (match.path) {
loading: state.showsStore.get("loading"), case "/shows/polochon":
shows: state.showsStore.get("shows"), return "/shows/polochon";
filter: state.showsStore.get("filter"), case "/shows/wishlist":
selectedImdbId: state.showsStore.get("selectedImdbId"), return "/wishlist/shows";
exploreOptions: state.showsStore.get("exploreOptions"), case "/shows/search/:search":
}; return "/shows/search/" + match.params.search;
} case "/shows/explore/:source/:category":
const mapDispatchToProps = { return (
selectShow, "/shows/explore?source=" +
showWishlistToggle, encodeURI(match.params.source) +
getShowDetails, "&category=" +
updateFilter, encodeURI(match.params.category)
);
default:
return null;
}
}; };
const ShowList = (props) => { const ShowList = ({ match, history }) => {
const dispatch = useDispatch();
useEffect(() => {
const url = fetchUrl(match);
if (url !== null) {
dispatch(fetchShows(url));
}
}, [dispatch, match]);
const loading = useSelector((state) => state.showsStore.get("loading"));
const shows = useSelector((state) => state.showsStore.get("shows"));
const filter = useSelector((state) => state.showsStore.get("filter"));
const selectedImdbId = useSelector((state) =>
state.showsStore.get("selectedImdbId")
);
const exploreOptions = useSelector((state) =>
state.showsStore.get("exploreOptions")
);
const showDetails = (imdbId) => { const showDetails = (imdbId) => {
props.history.push("/shows/details/" + imdbId); history.push("/shows/details/" + imdbId);
}; };
let selectedShow = Map(); let selectedShow = Map();
if (props.selectedImdbId !== "") { if (selectedImdbId !== "") {
selectedShow = props.shows.get(props.selectedImdbId); selectedShow = shows.get(selectedImdbId);
} }
const selectFunc = useCallback((id) => dispatch(selectShow(id)), [dispatch]);
const filterFunc = useCallback((filter) => dispatch(updateFilter(filter)), [
dispatch,
]);
const exploreFetchOptions = useCallback(
() => dispatch(getShowExploreOptions()),
[dispatch]
);
return ( return (
<div className="row" id="container"> <div className="row" id="container">
<ListPosters <ListPosters
data={props.shows} data={shows}
type="shows" type="shows"
placeHolder="Filter shows..." placeHolder="Filter shows..."
exploreOptions={props.exploreOptions} exploreOptions={exploreOptions}
updateFilter={props.updateFilter} exploreFetchOptions={exploreFetchOptions}
selectedImdbId={props.selectedImdbId} updateFilter={filterFunc}
filter={props.filter} selectedImdbId={selectedImdbId}
onClick={props.selectShow} filter={filter}
onClick={selectFunc}
onDoubleClick={showDetails} onDoubleClick={showDetails}
onKeyEnter={showDetails} onKeyEnter={showDetails}
params={props.match.params} params={match.params}
loading={props.loading} loading={loading}
/> />
<ListDetails <ListDetails
data={selectedShow} data={selectedShow}
loading={props.loading} loading={loading}
wishlist={() => wishlist={() =>
props.showWishlistToggle( dispatch(
showWishlistToggle(
isWishlisted(selectedShow), isWishlisted(selectedShow),
selectedShow.get("imdb_id") selectedShow.get("imdb_id")
) )
)
} }
> >
<span> <span>
@ -82,14 +119,5 @@ const ShowList = (props) => {
ShowList.propTypes = { ShowList.propTypes = {
match: PropTypes.object, match: PropTypes.object,
history: PropTypes.object, history: PropTypes.object,
shows: PropTypes.instanceOf(Map),
exploreOptions: PropTypes.instanceOf(Map),
selectedImdbId: PropTypes.string,
filter: PropTypes.string,
loading: PropTypes.bool,
showWishlistToggle: PropTypes.func,
selectShow: PropTypes.func,
getShowDetails: PropTypes.func,
updateFilter: PropTypes.func,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(ShowList); export default ShowList;

View File

@ -1,76 +0,0 @@
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.object,
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);

View File

@ -1,39 +1,28 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Map, List } from "immutable"; import { Map, List } from "immutable";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { prettySize } from "../../utils"; import { prettySize } from "../../utils";
import { import { addTorrent, removeTorrent } from "../../actions/torrents";
fetchTorrents,
addTorrent,
removeTorrent,
} from "../../actions/torrents";
const mapStateToProps = (state) => ({ const TorrentList = () => {
torrents: state.torrentStore.get("torrents"), const torrents = useSelector((state) => state.torrentStore.get("torrents"));
}); const dispatch = useDispatch();
const mapDispatchToProps = {
fetchTorrents,
addTorrent,
removeTorrent,
};
const TorrentList = (props) => ( return (
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
<AddTorrent addTorrent={props.addTorrent} /> <AddTorrent addTorrent={(url) => dispatch(addTorrent(url))} />
<Torrents torrents={props.torrents} removeTorrent={props.removeTorrent} /> <Torrents
torrents={torrents}
removeTorrent={(id) => dispatch(removeTorrent(id))}
/>
</div> </div>
</div> </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); export default TorrentList;
const AddTorrent = (props) => { const AddTorrent = (props) => {
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { addTorrent, searchTorrents } from "../../actions/torrents"; import { addTorrent, searchTorrents } from "../../actions/torrents";
import { Map, List } from "immutable"; import { Map, List } from "immutable";
import Loader from "../loader/loader"; import Loader from "../loader/loader";
@ -9,40 +9,38 @@ import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { prettySize } from "../../utils"; import { prettySize } from "../../utils";
const mapStateToProps = (state) => ({ const TorrentSearch = ({ history, match }) => {
searching: state.torrentStore.get("searching"), const dispatch = useDispatch();
results: state.torrentStore.get("searchResults"),
}); const searching = useSelector((state) => state.torrentStore.get("searching"));
const mapDispatchToProps = { addTorrent, searchTorrents }; const results = useSelector((state) =>
state.torrentStore.get("searchResults")
);
const TorrentSearch = ({
searching,
searchTorrents,
results,
addTorrent,
history,
match,
}) => {
const [search, setSearch] = useState(match.params.search || ""); const [search, setSearch] = useState(match.params.search || "");
const [type, setType] = useState(match.params.type || ""); const [type, setType] = useState(match.params.type || "");
const [url, setUrl] = useState("");
const getUrl = useCallback(() => { const url = useCallback(() => {
if (search === "" || type === "") {
return "";
}
return `/torrents/search/${type}/${encodeURI(search)}`; return `/torrents/search/${type}/${encodeURI(search)}`;
}, [type, search]); }, [type, search]);
useEffect(() => { const searchFunc = useCallback(() => {
if (search === "") { const searchURL = url();
return; if (searchURL === "") {
}
if (type === "") {
return; return;
} }
const url = getUrl(); dispatch(searchTorrents(searchURL));
searchTorrents(url); history.push(searchURL);
history.push(url); }, [dispatch, history, url]);
}, [url, getUrl, searchTorrents, history, search, type]);
useEffect(() => {
searchFunc();
}, [searchFunc]);
return ( return (
<div className="row"> <div className="row">
@ -61,7 +59,7 @@ const TorrentSearch = ({
typeFromURL={type} typeFromURL={type}
handleClick={() => { handleClick={() => {
setType("movies"); setType("movies");
setUrl(getUrl()); searchFunc();
}} }}
/> />
<SearchButton <SearchButton
@ -70,7 +68,7 @@ const TorrentSearch = ({
typeFromURL={type} typeFromURL={type}
handleClick={() => { handleClick={() => {
setType("shows"); setType("shows");
setUrl(getUrl()); searchFunc();
}} }}
/> />
</div> </div>
@ -79,7 +77,7 @@ const TorrentSearch = ({
<TorrentList <TorrentList
searching={searching} searching={searching}
results={results} results={results}
addTorrent={addTorrent} addTorrent={(url) => dispatch(addTorrent(url))}
searchFromURL={search} searchFromURL={search}
/> />
</div> </div>
@ -87,24 +85,20 @@ const TorrentSearch = ({
); );
}; };
TorrentSearch.propTypes = { TorrentSearch.propTypes = {
searching: PropTypes.bool.isRequired,
results: PropTypes.instanceOf(List),
searchFromURL: PropTypes.string, searchFromURL: PropTypes.string,
match: PropTypes.object, match: PropTypes.object,
history: PropTypes.object, history: PropTypes.object,
addTorrent: PropTypes.func.isRequired,
searchTorrents: PropTypes.func.isRequired,
}; };
const SearchButton = (props) => { const SearchButton = ({ type, typeFromURL, text, handleClick }) => {
const variant = props.type === props.typeFromURL ? "primary" : "secondary"; const variant = type === typeFromURL ? "primary" : "secondary";
return ( return (
<button <button
type="button" type="button"
className={`w-50 btn m-1 btn-lg btn-${variant}`} className={`w-50 btn m-1 btn-lg btn-${variant}`}
onClick={props.handleClick} onClick={handleClick}
> >
<i className="fa fa-search" aria-hidden="true"></i> {props.text} <i className="fa fa-search" aria-hidden="true"></i> {text}
</button> </button>
); );
}; };
@ -233,4 +227,4 @@ TorrentHealth.propTypes = {
leechers: PropTypes.number, leechers: PropTypes.number,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch); export default TorrentSearch;

View File

@ -1,19 +1,18 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import { useSelector } from "react-redux";
import { connect } from "react-redux";
import { Redirect, Link } from "react-router-dom"; import { Redirect, Link } from "react-router-dom";
const mapStateToProps = (state) => ({ const UserActivation = () => {
isActivated: state.userStore.get("isActivated"), const isLogged = useSelector((state) => state.userStore.get("isLogged"));
isLogged: state.userStore.get("isLogged"), const isActivated = useSelector((state) =>
}); state.userStore.get("isActivated")
);
const UserActivation = (props) => { if (!isLogged) {
if (!props.isLogged) {
return <Redirect to="/users/login" />; return <Redirect to="/users/login" />;
} }
if (props.isActivated) { if (isActivated) {
return <Redirect to="/" />; return <Redirect to="/" />;
} }
@ -31,9 +30,4 @@ const UserActivation = (props) => {
</div> </div>
); );
}; };
UserActivation.propTypes = { export default UserActivation;
isActivated: PropTypes.bool.isRequired,
isLogged: PropTypes.bool.isRequired,
};
export default connect(mapStateToProps)(UserActivation);

View File

@ -1,44 +1,30 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import Loader from "../loader/loader"; import Loader from "../loader/loader";
import { List } from "immutable";
import { getUserInfos, updateUser } from "../../actions/users"; import { getUserInfos, updateUser } from "../../actions/users";
import { getPolochons } from "../../actions/polochon"; import { getPolochons } from "../../actions/polochon";
import { PolochonSelect } from "../polochons/select"; import { PolochonSelect } from "../polochons/select";
const mapStateToProps = (state) => ({ export const UserEdit = () => {
loading: state.userStore.get("loading"), const dispatch = useDispatch();
publicPolochons: state.polochon.get("public"),
polochonId: state.userStore.get("polochonId"),
polochonActivated: state.userStore.get("polochonActivated"),
});
const mapDispatchToProps = {
updateUser,
getPolochons,
getUserInfos,
};
const UserEditConnect = ({
loading,
polochonId,
polochonActivated,
updateUser,
getPolochons,
getUserInfos,
publicPolochons,
}) => {
const [id, setId] = useState(polochonId); const [id, setId] = useState(polochonId);
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [passwordConfirm, setPasswordConfirm] = useState(""); const [passwordConfirm, setPasswordConfirm] = useState("");
const loading = useSelector((state) => state.userStore.get("loading"));
const publicPolochons = useSelector((state) => state.polochon.get("public"));
const polochonId = useSelector((state) => state.userStore.get("polochonId"));
const polochonActivated = useSelector((state) =>
state.userStore.get("polochonActivated")
);
useEffect(() => { useEffect(() => {
getPolochons(); dispatch(getPolochons());
getUserInfos(); dispatch(getUserInfos());
}, [getPolochons, getUserInfos]); }, [dispatch]);
useEffect(() => { useEffect(() => {
setId(polochonId); setId(polochonId);
@ -46,11 +32,13 @@ const UserEditConnect = ({
const handleSubmit = (ev) => { const handleSubmit = (ev) => {
ev.preventDefault(); ev.preventDefault();
dispatch(
updateUser({ updateUser({
password: password, password: password,
password_confirm: passwordConfirm, // eslint-disable-line camelcase password_confirm: passwordConfirm, // eslint-disable-line camelcase
polochon_id: id, // eslint-disable-line camelcase polochon_id: id, // eslint-disable-line camelcase
}); })
);
}; };
if (loading) { if (loading) {
@ -115,17 +103,3 @@ const UserEditConnect = ({
</div> </div>
); );
}; };
UserEditConnect.propTypes = {
loading: PropTypes.bool.isRequired,
polochonId: PropTypes.string,
polochonActivated: PropTypes.bool,
updateUser: PropTypes.func,
getPolochons: PropTypes.func,
getUserInfos: PropTypes.func,
publicPolochons: PropTypes.instanceOf(List),
};
export const UserEdit = connect(
mapStateToProps,
mapDispatchToProps
)(UserEditConnect);

View File

@ -1,29 +1,28 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Redirect, Link } from "react-router-dom"; import { Redirect, Link } from "react-router-dom";
import { loginUser } from "../../actions/users"; import { loginUser } from "../../actions/users";
const mapStateToProps = (state) => ({ const UserLoginForm = () => {
isLogged: state.userStore.get("isLogged"), const dispatch = useDispatch();
isLoading: state.userStore.get("loading"),
error: state.userStore.get("error"), const isLogged = useSelector((state) => state.userStore.get("isLogged"));
}); const isLoading = useSelector((state) => state.userStore.get("loading"));
const mapDispatchToProps = { loginUser }; const error = useSelector((state) => state.userStore.get("error"));
const UserLoginForm = (props) => {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
if (!props.isLoading) { if (!isLoading) {
props.loginUser(username, password); dispatch(loginUser(username, password));
} }
}; };
if (props.isLogged) { if (isLogged) {
return <Redirect to="/" />; return <Redirect to="/" />;
} }
@ -32,8 +31,8 @@ const UserLoginForm = (props) => {
<div className="col-10 offset-1 col-md-6 offset-md-3"> <div className="col-10 offset-1 col-md-6 offset-md-3">
<h2>Log in</h2> <h2>Log in</h2>
<hr /> <hr />
{props.error && props.error !== "" && ( {error && error !== "" && (
<div className="alert alert-danger">{props.error}</div> <div className="alert alert-danger">{error}</div>
)} )}
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}> <form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
<div> <div>
@ -66,12 +65,12 @@ const UserLoginForm = (props) => {
No account yet ? <Link to="/users/signup">Create one</Link> No account yet ? <Link to="/users/signup">Create one</Link>
</small> </small>
</span> </span>
{props.isLoading && ( {isLoading && (
<button className="btn btn-primary pull-right"> <button className="btn btn-primary pull-right">
<i className="fa fa-spinner fa-spin"></i> <i className="fa fa-spinner fa-spin"></i>
</button> </button>
)} )}
{props.isLoading || ( {isLoading || (
<span className="spaced-icons"> <span className="spaced-icons">
<input <input
className="btn btn-primary pull-right" className="btn btn-primary pull-right"
@ -87,11 +86,5 @@ const UserLoginForm = (props) => {
</div> </div>
); );
}; };
UserLoginForm.propTypes = {
loginUser: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
isLogged: PropTypes.bool.isRequired,
error: PropTypes.string,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm); export default UserLoginForm;

View File

@ -1,26 +1,18 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import { userLogout } from "../../actions/users"; import { userLogout } from "../../actions/users";
const mapStateToProps = (state) => ({ const UserLogout = () => {
isLogged: state.userStore.get("isLogged"), const dispatch = useDispatch();
}); const isLogged = useSelector((state) => state.userStore.get("isLogged"));
const mapDispatchToProps = { userLogout };
const UserLogout = (props) => { if (isLogged) {
if (props.isLogged) { dispatch(userLogout());
props.userLogout();
} }
return <Redirect to="/users/login" />; return <Redirect to="/users/login" />;
}; };
UserLogout.propTypes = {
isLogged: PropTypes.bool.isRequired,
userLogout: PropTypes.func.isRequired,
history: PropTypes.object,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserLogout); export default UserLogout;

View File

@ -1,7 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { Map } from "immutable";
import { PolochonList } from "../polochons/list"; import { PolochonList } from "../polochons/list";
import { UserEdit } from "./edit"; import { UserEdit } from "./edit";
@ -9,17 +7,16 @@ import { UserEdit } from "./edit";
import { getUserModules } from "../../actions/users"; import { getUserModules } from "../../actions/users";
import Modules from "../modules/modules"; import Modules from "../modules/modules";
const mapStateToProps = (state) => ({ const UserProfile = () => {
modules: state.userStore.get("modules"), const dispatch = useDispatch();
modulesLoading: state.userStore.get("modulesLoading"), const modules = useSelector((state) => state.userStore.get("modules"));
}); const modulesLoading = useSelector((state) =>
state.userStore.get("modulesLoading")
);
const mapDispatchToProps = { getUserModules };
const UserProfile = ({ modules, modulesLoading, getUserModules }) => {
useEffect(() => { useEffect(() => {
getUserModules(); dispatch(getUserModules());
}, [getUserModules]); }, [dispatch]);
return ( return (
<div> <div>
@ -29,10 +26,5 @@ const UserProfile = ({ modules, modulesLoading, getUserModules }) => {
</div> </div>
); );
}; };
UserProfile.propTypes = {
getUserModules: PropTypes.func.isRequired,
modules: PropTypes.instanceOf(Map),
modulesLoading: PropTypes.bool.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile); export default UserProfile;

View File

@ -1,33 +1,33 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import { userSignUp } from "../../actions/users"; import { userSignUp } from "../../actions/users";
const mapStateToProps = (state) => ({ const UserSignUp = () => {
isLogged: state.userStore.get("isLogged"), const dispatch = useDispatch();
isLoading: state.userStore.get("loading"),
error: state.userStore.get("error"),
});
const mapDispatchToProps = { userSignUp };
const UserSignUp = (props) => {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [passwordConfirm, setPasswordConfirm] = useState(""); const [passwordConfirm, setPasswordConfirm] = useState("");
if (props.isLogged) { const isLogged = useSelector((state) => state.userStore.get("isLogged"));
const isLoading = useSelector((state) => state.userStore.get("loading"));
const error = useSelector((state) => state.userStore.get("error"));
if (isLogged) {
return <Redirect to="/" />; return <Redirect to="/" />;
} }
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
props.userSignUp({ dispatch(
userSignUp({
username: username, username: username,
password: password, password: password,
password_confirm: passwordConfirm, // eslint-disable-line camelcase password_confirm: passwordConfirm, // eslint-disable-line camelcase
}); })
);
}; };
return ( return (
@ -35,8 +35,8 @@ const UserSignUp = (props) => {
<div className="col-10 offset-1 col-md-6 offset-md-3"> <div className="col-10 offset-1 col-md-6 offset-md-3">
<h2>Sign up</h2> <h2>Sign up</h2>
<hr /> <hr />
{props.error && props.error !== "" && ( {error && error !== "" && (
<div className="alert alert-danger">{props.error}</div> <div className="alert alert-danger">{error}</div>
)} )}
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}> <form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
@ -70,12 +70,12 @@ const UserSignUp = (props) => {
/> />
</div> </div>
<div> <div>
{props.isLoading && ( {isLoading && (
<button className="btn btn-primary pull-right"> <button className="btn btn-primary pull-right">
<i className="fa fa-spinner fa-spin"></i> <i className="fa fa-spinner fa-spin"></i>
</button> </button>
)} )}
{props.isLoading || ( {isLoading || (
<span> <span>
<input <input
className="btn btn-primary pull-right" className="btn btn-primary pull-right"
@ -90,11 +90,5 @@ const UserSignUp = (props) => {
</div> </div>
); );
}; };
UserSignUp.propTypes = {
isLogged: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
userSignUp: PropTypes.func.isRequired,
error: PropTypes.string,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserSignUp); export default UserSignUp;

View File

@ -1,40 +1,35 @@
import React, { useState } from "react"; import React, { useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { connect } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { UAParser } from "ua-parser-js"; import { UAParser } from "ua-parser-js";
import moment from "moment"; import moment from "moment";
import { Map, List } from "immutable"; import { Map } from "immutable";
import { getUserTokens, deleteUserToken } from "../../actions/users"; import { getUserTokens, deleteUserToken } from "../../actions/users";
const mapStateToProps = (state) => ({ const UserTokens = () => {
tokens: state.userStore.get("tokens"), const dispatch = useDispatch();
});
const mapDispatchToProps = { getUserTokens, deleteUserToken };
const UserTokens = (props) => { const tokens = useSelector((state) => state.userStore.get("tokens"));
const [fetched, setIsFetched] = useState(false);
if (!fetched) { useEffect(() => {
props.getUserTokens(); dispatch(getUserTokens());
setIsFetched(true); }, [dispatch]);
}
return ( return (
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
{props.tokens.map((el, index) => ( {tokens.map((el, index) => (
<Token key={index} data={el} deleteToken={props.deleteUserToken} /> <Token
key={index}
data={el}
deleteToken={(token) => dispatch(deleteUserToken(token))}
/>
))} ))}
</div> </div>
</div> </div>
); );
}; };
UserTokens.propTypes = {
tokens: PropTypes.instanceOf(List),
isLoading: PropTypes.bool,
getUserTokens: PropTypes.func.isRequired,
deleteUserToken: PropTypes.func.isRequired,
};
const Token = (props) => { const Token = (props) => {
const ua = UAParser(props.data.get("user_agent")); const ua = UAParser(props.data.get("user_agent"));
@ -171,4 +166,4 @@ Browser.propTypes = {
version: PropTypes.string, version: PropTypes.string,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(UserTokens); export default UserTokens;

View File

@ -1,25 +1,14 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import PropTypes from "prop-types"; import { useDispatch, useSelector } from "react-redux";
import { connect } from "react-redux";
import { setFetchedTorrents } from "../actions/torrents"; import { setFetchedTorrents } from "../actions/torrents";
import { newMovieEvent } from "../actions/movies"; import { newMovieEvent } from "../actions/movies";
import { newEpisodeEvent } from "../actions/shows"; import { newEpisodeEvent } from "../actions/shows";
const mapStateToProps = (state) => ({ const WsHandler = () => {
isLogged: state.userStore.get("isLogged"), const dispatch = useDispatch();
});
const mapDispatchToProps = { const isLogged = useSelector((state) => state.userStore.get("isLogged"));
setFetchedTorrents,
newMovieEvent,
newEpisodeEvent,
};
const WsHandler = ({
isLogged,
setFetchedTorrents,
newMovieEvent,
newEpisodeEvent,
}) => {
const [ws, setWs] = useState(null); const [ws, setWs] = useState(null);
const stop = useCallback(() => { const stop = useCallback(() => {
@ -73,13 +62,13 @@ const WsHandler = ({
switch (data.type) { switch (data.type) {
case "torrents": case "torrents":
setFetchedTorrents(data.message); dispatch(setFetchedTorrents(data.message));
break; break;
case "newVideo": case "newVideo":
if (data.message.type === "movie") { if (data.message.type === "movie") {
newMovieEvent(data.message.data); dispatch(newMovieEvent(data.message.data));
} else if (data.message.type === "episode") { } else if (data.message.type === "episode") {
newEpisodeEvent(data.message.data); dispatch(newEpisodeEvent(data.message.data));
} }
break; break;
} }
@ -90,7 +79,7 @@ const WsHandler = ({
}; };
setWs(socket); setWs(socket);
}, [ws, isLogged, newMovieEvent, setFetchedTorrents, newEpisodeEvent, stop]); }, [ws, isLogged, dispatch, stop]);
useEffect(() => { useEffect(() => {
const intervalID = setInterval(() => { const intervalID = setInterval(() => {
@ -118,11 +107,5 @@ const WsHandler = ({
return null; return null;
}; };
WsHandler.propTypes = {
isLogged: PropTypes.bool.isRequired,
setFetchedTorrents: PropTypes.func,
newMovieEvent: PropTypes.func,
newEpisodeEvent: PropTypes.func,
};
export default connect(mapStateToProps, mapDispatchToProps)(WsHandler); export default WsHandler;

View File

@ -3,7 +3,7 @@
"scripts": { "scripts": {
"start": "NODE_ENV=development ./node_modules/webpack/bin/webpack.js -d --progress --colors --watch", "start": "NODE_ENV=development ./node_modules/webpack/bin/webpack.js -d --progress --colors --watch",
"build": "NODE_ENV=production ./node_modules/webpack/bin/webpack.js -p --progress --colors", "build": "NODE_ENV=production ./node_modules/webpack/bin/webpack.js -p --progress --colors",
"lint": "./node_modules/eslint/bin/eslint.js ." "lint": "./node_modules/eslint/bin/eslint.js js"
}, },
"dependencies": { "dependencies": {
"bootstrap": "^4.4.1", "bootstrap": "^4.4.1",