Compare commits

..

No commits in common. "817da07a2d7f9c7dde82d70e84e0735745a84cfc" and "937b12bb6722898a18d1b954aa366ed76edf4fdc" have entirely different histories.

40 changed files with 967 additions and 665 deletions

View File

@ -12,7 +12,6 @@ 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,10 +28,12 @@ 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";
@ -59,23 +61,31 @@ const App = () => (
exact exact
component={TorrentSearch} component={TorrentSearch}
/> />
<Route path="/movies/polochon" exact component={MovieList} /> <MoviesRoute path="/movies/polochon" exact component={MovieList} />
<Route path="/movies/wishlist" exact component={MovieList} /> <MoviesRoute path="/movies/wishlist" exact component={MovieList} />
<Route path="/movies/search/:search" exact component={MovieList} /> <MoviesRoute
<Route path="/movies/search/:search"
exact
component={MovieList}
/>
<MoviesRoute
path="/movies/explore/:source/:category" path="/movies/explore/:source/:category"
exact exact
component={MovieList} component={MovieList}
/> />
<Route path="/shows/polochon" exact component={ShowList} /> <ShowsRoute path="/shows/polochon" exact component={ShowList} />
<Route path="/shows/wishlist" exact component={ShowList} /> <ShowsRoute path="/shows/wishlist" exact component={ShowList} />
<Route path="/shows/search/:search" exact component={ShowList} /> <ShowsRoute path="/shows/search/:search" exact component={ShowList} />
<Route <ShowsRoute
path="/shows/explore/:source/:category" path="/shows/explore/:source/:category"
exact exact
component={ShowList} component={ShowList}
/> />
<Route path="/shows/details/:imdbId" exact component={ShowDetails} /> <ShowsRoute
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,19 +1,17 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux"; import { connect } 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";
export const ProtectedRoute = ({ component: Component, ...otherProps }) => { const protectedRoute = ({
const dispatch = useDispatch(); component: Component,
isLogged,
const isLogged = useSelector((state) => state.userStore.get("isLogged")); isActivated,
const isActivated = useSelector((state) => isTokenSet,
state.userStore.get("isActivated") setUserToken,
); ...otherProps
const isTokenSet = useSelector((state) => state.userStore.get("isTokenSet")); }) => {
const isAuthenticated = () => { const isAuthenticated = () => {
if (isTokenSet) { if (isTokenSet) {
return true; return true;
@ -22,7 +20,7 @@ export const ProtectedRoute = ({ component: Component, ...otherProps }) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (isLogged || (token && token !== "")) { if (isLogged || (token && token !== "")) {
if (!isTokenSet) { if (!isTokenSet) {
dispatch(setUserToken(token)); setUserToken(token);
} }
return true; return true;
} }
@ -47,12 +45,24 @@ export const ProtectedRoute = ({ component: Component, ...otherProps }) => {
/> />
); );
}; };
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);
export const AdminRoute = ({ component: Component, ...otherProps }) => { const adminRoute = ({ component: Component, isAdmin, ...otherProps }) => {
const isAdmin = useSelector((state) => state.userStore.get("isAdmin"));
return ( return (
<Route <Route
{...otherProps} {...otherProps}
@ -66,6 +76,10 @@ export const AdminRoute = ({ component: Component, ...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,20 +1,28 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
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";
export const AdminModules = () => { const AdminModulesConnected = ({ modules, loading, getAdminModules }) => {
const dispatch = useDispatch();
const loading = useSelector((state) =>
state.adminStore.get("fetchingModules")
);
const modules = useSelector((state) => state.adminStore.get("modules"));
useEffect(() => { useEffect(() => {
dispatch(getAdminModules()); getAdminModules();
}, [dispatch]); }, [getAdminModules]);
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,17 +1,15 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Stat } from "./stat"; import { Stat } from "./stat";
import { getStats } from "../../actions/admins"; import { getStats } from "../../actions/admins";
export const Stats = () => { const StatsConnected = ({ stats, getStats }) => {
const dispatch = useDispatch();
const stats = useSelector((state) => state.adminStore.get("stats"));
useEffect(() => { useEffect(() => {
dispatch(getStats()); getStats();
}, [dispatch]); }, [getStats]);
return ( return (
<div className="row d-flex flex-wrap"> <div className="row d-flex flex-wrap">
@ -36,3 +34,13 @@ export const Stats = () => {
</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,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux"; import { connect } from "react-redux";
import { List } from "immutable";
import { updateUser, deleteUser } from "../../actions/admins"; import { updateUser, deleteUser } from "../../actions/admins";
@ -10,7 +11,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";
export const UserEdit = ({ const UserEditConnect = ({
id, id,
name, name,
admin: initAdmin, admin: initAdmin,
@ -18,10 +19,10 @@ export const UserEdit = ({
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);
@ -37,7 +38,6 @@ export const UserEdit = ({
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
dispatch(
updateUser({ updateUser({
userId: id, userId: id,
polochonToken: token, polochonToken: token,
@ -46,8 +46,7 @@ export const UserEdit = ({
polochonId, polochonId,
polochonActivated, polochonActivated,
password, password,
}) });
);
setModal(false); setModal(false);
}; };
@ -56,7 +55,7 @@ export const UserEdit = ({
e.preventDefault(); e.preventDefault();
} }
if (confirmDelete) { if (confirmDelete) {
dispatch(deleteUser(name)); deleteUser(name);
setModal(false); setModal(false);
} else { } else {
setConfirmDelete(true); setConfirmDelete(true);
@ -138,12 +137,23 @@ export const UserEdit = ({
</span> </span>
); );
}; };
UserEdit.propTypes = { UserEditConnect.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,19 +1,23 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
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";
export const UserList = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); users: state.adminStore.get("users"),
const users = useSelector((state) => state.adminStore.get("users")); });
const mapDispatchToProps = { getUsers, getPolochons };
const UserListConnect = ({ users, getUsers, getPolochons }) => {
useEffect(() => { useEffect(() => {
dispatch(getUsers()); getUsers();
dispatch(getPolochons()); getPolochons();
}, [dispatch]); }, [getUsers, getPolochons]);
return ( return (
<div className="table-responsive my-2"> <div className="table-responsive my-2">
@ -50,3 +54,13 @@ export const UserList = () => {
</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,27 +1,35 @@
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 { useSelector, useDispatch } from "react-redux"; import { connect } from "react-redux";
import { dismissAlert } from "../../actions/alerts"; import { dismissAlert } from "../../actions/alerts";
const Alert = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); show: state.alerts.get("show"),
title: state.alerts.get("message"),
type: state.alerts.get("type"),
});
const mapDispatchToProps = { dismissAlert };
const show = useSelector((state) => state.alerts.get("show")); const Alert = (props) => {
const title = useSelector((state) => state.alerts.get("message")); if (!props.show) {
const type = useSelector((state) => state.alerts.get("type"));
if (!show) {
return null; return null;
} }
return ( return (
<SweetAlert <SweetAlert
type={type} type={props.type}
title={title} title={props.title}
onConfirm={() => dispatch(dismissAlert())} onConfirm={props.dismissAlert}
/> />
); );
}; };
Alert.propTypes = {
show: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
dismissAlert: PropTypes.func.isRequired,
type: PropTypes.string,
};
export default Alert; export default connect(mapStateToProps, mapDispatchToProps)(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 { useDispatch } from "react-redux"; import { connect } 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;
} }
export const TorrentsButton = ({ torrents, search, searching, url }) => { const torrentsButton = ({ torrents, search, searching, addTorrent, url }) => {
const dispatch = useDispatch(); /* eslint-disable */
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,10 +97,7 @@ export const TorrentsButton = ({ torrents, search, searching, url }) => {
return <Dropdown.Divider key={index} />; return <Dropdown.Divider key={index} />;
case "entry": case "entry":
return ( return (
<Dropdown.Item <Dropdown.Item key={index} onClick={() => addTorrent(e.url)}>
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>
@ -114,12 +111,15 @@ export const TorrentsButton = ({ torrents, search, searching, 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,20 +1,10 @@
import React, { useEffect } from "react"; import React 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 = ({ const ExplorerOptions = ({ display, params, options, type, history }) => {
display, // Should this componennt be displayed
params,
options,
type,
history,
fetch,
}) => {
useEffect(() => {
fetch();
}, [fetch]);
if (!display) { if (!display) {
return null; return null;
} }
@ -113,7 +103,6 @@ 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,7 +55,6 @@ 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
@ -82,7 +81,6 @@ 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,14 +1,11 @@
import React, { useEffect, useCallback } from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux"; import { OrderedMap, Map } from "immutable";
import { Map } from "immutable"; import { connect } from "react-redux";
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";
@ -21,87 +18,55 @@ import { ShowMore } from "../buttons/showMore";
import { MovieSubtitlesButton } from "./subtitlesButton"; import { MovieSubtitlesButton } from "./subtitlesButton";
import { MovieTorrentsButton } from "./torrentsButton"; import { MovieTorrentsButton } from "./torrentsButton";
const fetchUrl = (match) => { function mapStateToProps(state) {
switch (match.path) { return {
case "/movies/polochon": loading: state.movieStore.get("loading"),
return "/movies/polochon"; movies: state.movieStore.get("movies"),
case "/movies/wishlist": filter: state.movieStore.get("filter"),
return "/wishlist/movies"; selectedImdbId: state.movieStore.get("selectedImdbId"),
case "/movies/search/:search": exploreOptions: state.movieStore.get("exploreOptions"),
return "/movies/search/" + match.params.search; };
case "/movies/explore/:source/:category": }
return ( const mapDispatchToProps = {
"/movies/explore?source=" + selectMovie,
encodeURI(match.params.source) + updateFilter,
"&category=" + movieWishlistToggle,
encodeURI(match.params.category)
);
default:
return null;
}
}; };
const MovieList = ({ match }) => { const MovieList = (props) => {
const dispatch = useDispatch();
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(); let selectedMovie = Map();
if (movies !== undefined && movies.has(selectedImdbId)) { if (props.movies !== undefined && props.movies.has(props.selectedImdbId)) {
selectedMovie = movies.get(selectedImdbId); selectedMovie = props.movies.get(props.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={movies} data={props.movies}
type="movies" type="movies"
placeHolder="Filter movies..." placeHolder="Filter movies..."
exploreFetchOptions={exploreFetchOptions} exploreOptions={props.exploreOptions}
exploreOptions={exploreOptions} selectedImdbId={props.selectedImdbId}
selectedImdbId={selectedImdbId} updateFilter={props.updateFilter}
updateFilter={filterFunc} filter={props.filter}
filter={filter} onClick={props.selectMovie}
onClick={selectFunc} onDoubleClick={function () {
onDoubleClick={() => {}} return;
onKeyEnter={() => {}} }}
params={match.params} onKeyEnter={function () {
loading={loading} return;
}}
params={props.match.params}
loading={props.loading}
/> />
<ListDetails <ListDetails
data={selectedMovie} data={selectedMovie}
loading={loading} loading={props.loading}
wishlist={() => wishlist={() =>
dispatch( props.movieWishlistToggle(
movieWishlistToggle(
selectedMovie.get("imdb_id"), selectedMovie.get("imdb_id"),
isWishlisted(selectedMovie) isWishlisted(selectedMovie)
) )
)
} }
> >
<ShowMore <ShowMore
@ -126,12 +91,15 @@ const MovieList = ({ match }) => {
); );
}; };
MovieList.propTypes = { MovieList.propTypes = {
fetchMovies: PropTypes.func, movies: PropTypes.instanceOf(OrderedMap),
getMovieExploreOptions: PropTypes.func, exploreOptions: PropTypes.instanceOf(Map),
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 MovieList; export default connect(mapStateToProps, mapDispatchToProps)(MovieList);

View File

@ -0,0 +1,65 @@
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,32 +1,35 @@
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 { useDispatch } from "react-redux"; import { connect } from "react-redux";
import { searchMovieSubtitles } from "../../actions/subtitles"; import { searchMovieSubtitles } from "../../actions/subtitles";
import { SubtitlesButton } from "../buttons/subtitles"; import { SubtitlesButton } from "../buttons/subtitles";
export const MovieSubtitlesButton = ({ 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={() => dispatch(searchMovieSubtitles(imdbId))} search={() => 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,26 +1,33 @@
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 { useDispatch } from "react-redux"; import { connect } from "react-redux";
import { getMovieDetails } from "../../actions/movies"; import { getMovieDetails } from "../../actions/movies";
import { TorrentsButton } from "../buttons/torrents"; import { TorrentsButton } from "../buttons/torrents";
export const MovieTorrentsButton = ({ torrents, imdbId, title, searching }) => { const movieTorrentsButton = ({
const dispatch = useDispatch(); torrents,
return ( imdbId,
title,
searching,
getMovieDetails,
}) => (
<TorrentsButton <TorrentsButton
torrents={torrents} torrents={torrents}
searching={searching} searching={searching}
search={() => dispatch(getMovieDetails(imdbId))} search={() => 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 { useSelector } from "react-redux"; import { connect } 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,14 +8,23 @@ 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 AppNavBar = () => { const mapStateToProps = (state) => {
const [expanded, setExpanded] = useState(false); 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 username = useSelector((state) => state.userStore.get("username")); const AppNavBar = (props) => {
const isAdmin = useSelector((state) => state.userStore.get("isAdmin")); const [expanded, setExpanded] = useState(false);
const torrentCount = useSelector(
(state) => state.torrentStore.get("torrents").size
);
return ( return (
<Navbar <Navbar
@ -36,7 +45,7 @@ const AppNavBar = () => {
<MoviesDropdown /> <MoviesDropdown />
<ShowsDropdown /> <ShowsDropdown />
<WishlistDropdown /> <WishlistDropdown />
<TorrentsDropdown torrentsCount={torrentCount} /> <TorrentsDropdown torrentsCount={props.torrentCount} />
</Nav> </Nav>
<Nav> <Nav>
<Route <Route
@ -61,16 +70,20 @@ const AppNavBar = () => {
/> />
)} )}
/> />
<UserDropdown username={username} isAdmin={isAdmin} /> <UserDropdown username={props.username} isAdmin={props.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 { useDispatch } from "react-redux"; import { connect } from "react-redux";
import { Toast } from "react-bootstrap"; import { Toast } from "react-bootstrap";
import { removeNotification } from "../../actions/notifications"; import { removeNotification } from "../../actions/notifications";
export const Notification = ({ const NotificationConnected = ({
id, id,
icon, icon,
title, title,
@ -14,13 +14,13 @@ export const Notification = ({
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(() => dispatch(removeNotification(id)), 200); setTimeout(() => removeNotification(id), 200);
}; };
return ( return (
@ -38,7 +38,7 @@ export const Notification = ({
</Toast> </Toast>
); );
}; };
Notification.propTypes = { NotificationConnected.propTypes = {
id: PropTypes.string, id: PropTypes.string,
icon: PropTypes.string, icon: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
@ -46,9 +46,10 @@ Notification.propTypes = {
imageUrl: PropTypes.string, imageUrl: PropTypes.string,
autohide: PropTypes.bool, autohide: PropTypes.bool,
delay: PropTypes.number, delay: PropTypes.number,
removeNotification: PropTypes.func,
}; };
Notification.defaultProps = { NotificationConnected.defaultProps = {
autohide: false, autohide: false,
delay: 5000, delay: 5000,
icon: "", icon: "",
@ -56,3 +57,7 @@ Notification.defaultProps = {
title: "Info", title: "Info",
message: "", message: "",
}; };
export const Notification = connect(null, { removeNotification })(
NotificationConnected
);

View File

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

View File

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

View File

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

View File

@ -1,18 +1,25 @@
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 { useDispatch } from "react-redux"; import { connect } 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 Polochon = ({ id, name, token, url, authToken, users }) => { export const PolochonConnected = ({
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">
@ -26,7 +33,7 @@ export const Polochon = ({ id, name, token, url, authToken, users }) => {
/> />
<i <i
className="fa fa-trash clickable" className="fa fa-trash clickable"
onClick={() => dispatch(deletePolochon(id))} onClick={() => deletePolochon(id)}
/> />
</span> </span>
</div> </div>
@ -42,7 +49,7 @@ export const Polochon = ({ id, name, token, url, authToken, users }) => {
title="Polochon config" title="Polochon config"
show={edit} show={edit}
setShow={setEdit} setShow={setEdit}
update={(params) => dispatch(updatePolochon(params))} update={updatePolochon}
id={id} id={id}
initialName={name} initialName={name}
initialUrl={url} initialUrl={url}
@ -51,11 +58,17 @@ export const Polochon = ({ id, name, token, url, authToken, users }) => {
</React.Fragment> </React.Fragment>
); );
}; };
Polochon.propTypes = { PolochonConnected.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 { useDispatch } from "react-redux"; import { connect } 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 PolochonUser = ({ export const PolochonUserConnected = ({
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,14 +27,12 @@ export const PolochonUser = ({
}, [initialActivated, initialToken]); }, [initialActivated, initialToken]);
const handleSubmit = () => { const handleSubmit = () => {
dispatch(
editPolochonUser({ editPolochonUser({
polochonId, polochonId,
id, id,
token, token,
activated, activated,
}) });
);
setEdit(false); setEdit(false);
}; };
@ -69,7 +67,7 @@ export const PolochonUser = ({
</tr> </tr>
); );
}; };
PolochonUser.propTypes = { PolochonUserConnected.propTypes = {
polochonId: PropTypes.string, polochonId: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
@ -77,3 +75,7 @@ PolochonUser.propTypes = {
initialActivated: PropTypes.bool, initialActivated: PropTypes.bool,
editPolochonUser: PropTypes.func, editPolochonUser: PropTypes.func,
}; };
export const PolochonUser = connect(null, { editPolochonUser })(
PolochonUserConnected
);

View File

@ -1,6 +1,7 @@
import React, { useEffect } from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux"; import { Map } from "immutable";
import { connect } from "react-redux";
import Loader from "../loader/loader"; import Loader from "../loader/loader";
@ -8,17 +9,12 @@ 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";
import { fetchShowDetails } from "../../actions/shows"; const mapStateToProps = (state) => ({
loading: state.showStore.get("loading"),
export const ShowDetails = ({ match }) => { show: state.showStore.get("show"),
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 />;
} }
@ -33,6 +29,8 @@ export const ShowDetails = ({ match }) => {
</React.Fragment> </React.Fragment>
); );
}; };
ShowDetails.propTypes = { showDetails.propTypes = {
match: PropTypes.object.isRequired, loading: PropTypes.bool,
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 { useDispatch, useSelector } from "react-redux"; import { connect } from "react-redux";
import { showWishlistToggle } from "../../../actions/shows"; import { showWishlistToggle } from "../../../actions/shows";
@ -24,17 +24,12 @@ import { EpisodeSubtitlesButton } from "./subtitlesButton";
import { EpisodeThumb } from "./episodeThumb"; import { EpisodeThumb } from "./episodeThumb";
import { EpisodeTorrentsButton } from "./torrentsButton"; import { EpisodeTorrentsButton } from "./torrentsButton";
export const Episode = (props) => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); trackedSeason: state.showStore.getIn(["show", "tracked_season"], null),
trackedEpisode: state.showStore.getIn(["show", "tracked_episode"], null),
});
const trackedSeason = useSelector((state) => const episode = (props) => (
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">
@ -42,18 +37,16 @@ export 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,
trackedSeason, props.trackedSeason,
trackedEpisode props.trackedEpisode
)} )}
wishlist={() => wishlist={() =>
dispatch( props.showWishlistToggle(
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")} />
@ -103,9 +96,15 @@ export const Episode = (props) => {
</ShowMore> </ShowMore>
</div> </div>
</div> </div>
); );
}; episode.propTypes = {
Episode.propTypes = {
data: PropTypes.instanceOf(Map).isRequired, data: PropTypes.instanceOf(Map).isRequired,
trackedSeason: PropTypes.number,
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 { useDispatch } from "react-redux"; import { connect } from "react-redux";
import { isWishlisted } from "../../../utils"; import { isWishlisted } from "../../../utils";
@ -15,9 +15,7 @@ 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">
@ -33,12 +31,10 @@ export const Header = (props) => {
title={props.data.get("title")} title={props.data.get("title")}
wishlisted={isWishlisted(props.data)} wishlisted={isWishlisted(props.data)}
wishlist={() => wishlist={() =>
dispatch( props.showWishlistToggle(
showWishlistToggle(
isWishlisted(props.data), isWishlisted(props.data),
props.data.get("imdb_id") props.data.get("imdb_id")
) )
)
} }
/> />
</p> </p>
@ -65,8 +61,10 @@ export const Header = (props) => {
</div> </div>
</div> </div>
</div> </div>
); );
}; header.propTypes = {
Header.propTypes = {
data: PropTypes.instanceOf(Map), data: PropTypes.instanceOf(Map),
showWishlistToggle: PropTypes.func,
}; };
export const Header = connect(null, { showWishlistToggle })(header);

View File

@ -1,37 +1,39 @@
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 { useDispatch } from "react-redux"; import { connect } from "react-redux";
import { searchEpisodeSubtitles } from "../../../actions/subtitles"; import { searchEpisodeSubtitles } from "../../../actions/subtitles";
import { SubtitlesButton } from "../../buttons/subtitles"; import { SubtitlesButton } from "../../buttons/subtitles";
export const EpisodeSubtitlesButton = ({ 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={() => dispatch(searchEpisodeSubtitles(imdbId, season, episode))} search={() => 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,39 +1,40 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch } from "react-redux"; import { connect } 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";
export const EpisodeTorrentsButton = ({ 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={() => dispatch(getEpisodeDetails(imdbId, season, episode))} search={() => 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,14 +1,12 @@
import React, { useEffect, useCallback } 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 { useSelector, useDispatch } from "react-redux"; import { connect } 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";
@ -16,91 +14,56 @@ import { isWishlisted } from "../../utils";
import ListDetails from "../list/details"; import ListDetails from "../list/details";
import ListPosters from "../list/posters"; import ListPosters from "../list/posters";
const fetchUrl = (match) => { function mapStateToProps(state) {
switch (match.path) { return {
case "/shows/polochon": loading: state.showsStore.get("loading"),
return "/shows/polochon"; shows: state.showsStore.get("shows"),
case "/shows/wishlist": filter: state.showsStore.get("filter"),
return "/wishlist/shows"; selectedImdbId: state.showsStore.get("selectedImdbId"),
case "/shows/search/:search": exploreOptions: state.showsStore.get("exploreOptions"),
return "/shows/search/" + match.params.search; };
case "/shows/explore/:source/:category": }
return ( const mapDispatchToProps = {
"/shows/explore?source=" + selectShow,
encodeURI(match.params.source) + showWishlistToggle,
"&category=" + getShowDetails,
encodeURI(match.params.category) updateFilter,
);
default:
return null;
}
}; };
const ShowList = ({ match, history }) => { const ShowList = (props) => {
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) => {
history.push("/shows/details/" + imdbId); props.history.push("/shows/details/" + imdbId);
}; };
let selectedShow = Map(); let selectedShow = Map();
if (selectedImdbId !== "") { if (props.selectedImdbId !== "") {
selectedShow = shows.get(selectedImdbId); selectedShow = props.shows.get(props.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={shows} data={props.shows}
type="shows" type="shows"
placeHolder="Filter shows..." placeHolder="Filter shows..."
exploreOptions={exploreOptions} exploreOptions={props.exploreOptions}
exploreFetchOptions={exploreFetchOptions} updateFilter={props.updateFilter}
updateFilter={filterFunc} selectedImdbId={props.selectedImdbId}
selectedImdbId={selectedImdbId} filter={props.filter}
filter={filter} onClick={props.selectShow}
onClick={selectFunc}
onDoubleClick={showDetails} onDoubleClick={showDetails}
onKeyEnter={showDetails} onKeyEnter={showDetails}
params={match.params} params={props.match.params}
loading={loading} loading={props.loading}
/> />
<ListDetails <ListDetails
data={selectedShow} data={selectedShow}
loading={loading} loading={props.loading}
wishlist={() => wishlist={() =>
dispatch( props.showWishlistToggle(
showWishlistToggle(
isWishlisted(selectedShow), isWishlisted(selectedShow),
selectedShow.get("imdb_id") selectedShow.get("imdb_id")
) )
)
} }
> >
<span> <span>
@ -119,5 +82,14 @@ const ShowList = ({ match, history }) => {
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 ShowList; export default connect(mapStateToProps, mapDispatchToProps)(ShowList);

View File

@ -0,0 +1,76 @@
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,28 +1,39 @@
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 { useDispatch, useSelector } from "react-redux"; import { connect } from "react-redux";
import { prettySize } from "../../utils"; import { prettySize } from "../../utils";
import { addTorrent, removeTorrent } from "../../actions/torrents"; import {
fetchTorrents,
addTorrent,
removeTorrent,
} from "../../actions/torrents";
const TorrentList = () => { const mapStateToProps = (state) => ({
const torrents = useSelector((state) => state.torrentStore.get("torrents")); torrents: state.torrentStore.get("torrents"),
const dispatch = useDispatch(); });
const mapDispatchToProps = {
fetchTorrents,
addTorrent,
removeTorrent,
};
return ( const TorrentList = (props) => (
<div className="row"> <div className="row">
<div className="col-12"> <div className="col-12">
<AddTorrent addTorrent={(url) => dispatch(addTorrent(url))} /> <AddTorrent addTorrent={props.addTorrent} />
<Torrents <Torrents torrents={props.torrents} removeTorrent={props.removeTorrent} />
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 TorrentList; export default connect(mapStateToProps, mapDispatchToProps)(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 { useDispatch, useSelector } from "react-redux"; import { connect } 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,38 +9,40 @@ import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { prettySize } from "../../utils"; import { prettySize } from "../../utils";
const TorrentSearch = ({ history, match }) => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); searching: state.torrentStore.get("searching"),
results: state.torrentStore.get("searchResults"),
const searching = useSelector((state) => state.torrentStore.get("searching")); });
const results = useSelector((state) => const mapDispatchToProps = { addTorrent, searchTorrents };
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 url = useCallback(() => { const getUrl = useCallback(() => {
if (search === "" || type === "") {
return "";
}
return `/torrents/search/${type}/${encodeURI(search)}`; return `/torrents/search/${type}/${encodeURI(search)}`;
}, [type, search]); }, [type, search]);
const searchFunc = useCallback(() => { useEffect(() => {
const searchURL = url(); if (search === "") {
if (searchURL === "") { return;
}
if (type === "") {
return; return;
} }
dispatch(searchTorrents(searchURL)); const url = getUrl();
history.push(searchURL); searchTorrents(url);
}, [dispatch, history, url]); history.push(url);
}, [url, getUrl, searchTorrents, history, search, type]);
useEffect(() => {
searchFunc();
}, [searchFunc]);
return ( return (
<div className="row"> <div className="row">
@ -59,7 +61,7 @@ const TorrentSearch = ({ history, match }) => {
typeFromURL={type} typeFromURL={type}
handleClick={() => { handleClick={() => {
setType("movies"); setType("movies");
searchFunc(); setUrl(getUrl());
}} }}
/> />
<SearchButton <SearchButton
@ -68,7 +70,7 @@ const TorrentSearch = ({ history, match }) => {
typeFromURL={type} typeFromURL={type}
handleClick={() => { handleClick={() => {
setType("shows"); setType("shows");
searchFunc(); setUrl(getUrl());
}} }}
/> />
</div> </div>
@ -77,7 +79,7 @@ const TorrentSearch = ({ history, match }) => {
<TorrentList <TorrentList
searching={searching} searching={searching}
results={results} results={results}
addTorrent={(url) => dispatch(addTorrent(url))} addTorrent={addTorrent}
searchFromURL={search} searchFromURL={search}
/> />
</div> </div>
@ -85,20 +87,24 @@ const TorrentSearch = ({ history, match }) => {
); );
}; };
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 = ({ type, typeFromURL, text, handleClick }) => { const SearchButton = (props) => {
const variant = type === typeFromURL ? "primary" : "secondary"; const variant = props.type === props.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={handleClick} onClick={props.handleClick}
> >
<i className="fa fa-search" aria-hidden="true"></i> {text} <i className="fa fa-search" aria-hidden="true"></i> {props.text}
</button> </button>
); );
}; };
@ -227,4 +233,4 @@ TorrentHealth.propTypes = {
leechers: PropTypes.number, leechers: PropTypes.number,
}; };
export default TorrentSearch; export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);

View File

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

View File

@ -1,30 +1,44 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
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";
export const UserEdit = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); loading: state.userStore.get("loading"),
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(() => {
dispatch(getPolochons()); getPolochons();
dispatch(getUserInfos()); getUserInfos();
}, [dispatch]); }, [getPolochons, getUserInfos]);
useEffect(() => { useEffect(() => {
setId(polochonId); setId(polochonId);
@ -32,13 +46,11 @@ export const UserEdit = () => {
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) {
@ -103,3 +115,17 @@ export const UserEdit = () => {
</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,28 +1,29 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux"; import { connect } 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 UserLoginForm = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); isLogged: state.userStore.get("isLogged"),
isLoading: state.userStore.get("loading"),
const isLogged = useSelector((state) => state.userStore.get("isLogged")); error: state.userStore.get("error"),
const isLoading = useSelector((state) => state.userStore.get("loading")); });
const error = useSelector((state) => state.userStore.get("error")); const mapDispatchToProps = { loginUser };
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 (!isLoading) { if (!props.isLoading) {
dispatch(loginUser(username, password)); props.loginUser(username, password);
} }
}; };
if (isLogged) { if (props.isLogged) {
return <Redirect to="/" />; return <Redirect to="/" />;
} }
@ -31,8 +32,8 @@ const UserLoginForm = () => {
<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 />
{error && error !== "" && ( {props.error && props.error !== "" && (
<div className="alert alert-danger">{error}</div> <div className="alert alert-danger">{props.error}</div>
)} )}
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}> <form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
<div> <div>
@ -65,12 +66,12 @@ const UserLoginForm = () => {
No account yet ? <Link to="/users/signup">Create one</Link> No account yet ? <Link to="/users/signup">Create one</Link>
</small> </small>
</span> </span>
{isLoading && ( {props.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>
)} )}
{isLoading || ( {props.isLoading || (
<span className="spaced-icons"> <span className="spaced-icons">
<input <input
className="btn btn-primary pull-right" className="btn btn-primary pull-right"
@ -86,5 +87,11 @@ const UserLoginForm = () => {
</div> </div>
); );
}; };
UserLoginForm.propTypes = {
loginUser: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
isLogged: PropTypes.bool.isRequired,
error: PropTypes.string,
};
export default UserLoginForm; export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm);

View File

@ -1,18 +1,26 @@
import React from "react"; import React from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
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 UserLogout = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); isLogged: state.userStore.get("isLogged"),
const isLogged = useSelector((state) => state.userStore.get("isLogged")); });
const mapDispatchToProps = { userLogout };
if (isLogged) { const UserLogout = (props) => {
dispatch(userLogout()); if (props.isLogged) {
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 UserLogout; export default connect(mapStateToProps, mapDispatchToProps)(UserLogout);

View File

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

View File

@ -1,33 +1,33 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types";
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 UserSignUp = () => { const mapStateToProps = (state) => ({
const dispatch = useDispatch(); isLogged: state.userStore.get("isLogged"),
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("");
const isLogged = useSelector((state) => state.userStore.get("isLogged")); if (props.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();
dispatch( props.userSignUp({
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 = () => {
<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 />
{error && error !== "" && ( {props.error && props.error !== "" && (
<div className="alert alert-danger">{error}</div> <div className="alert alert-danger">{props.error}</div>
)} )}
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}> <form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
@ -70,12 +70,12 @@ const UserSignUp = () => {
/> />
</div> </div>
<div> <div>
{isLoading && ( {props.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>
)} )}
{isLoading || ( {props.isLoading || (
<span> <span>
<input <input
className="btn btn-primary pull-right" className="btn btn-primary pull-right"
@ -90,5 +90,11 @@ const UserSignUp = () => {
</div> </div>
); );
}; };
UserSignUp.propTypes = {
isLogged: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
userSignUp: PropTypes.func.isRequired,
error: PropTypes.string,
};
export default UserSignUp; export default connect(mapStateToProps, mapDispatchToProps)(UserSignUp);

View File

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

View File

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