diff --git a/client/app/components/Admin.jsx b/client/app/components/Admin.jsx
new file mode 100644
index 0000000..96d36af
--- /dev/null
+++ b/client/app/components/Admin.jsx
@@ -0,0 +1,35 @@
+/*
+ ./app/components/App.jsx
+
+
+*/
+import React from 'react';
+import { Link } from 'react-router-dom'
+
+import FetchData from './FetchData.js';
+import ListStrays from './ListStrays.jsx';
+
+import FetchRequested from './FetchRequested.jsx';
+
+import LoginForm from './LoginForm/LoginForm.jsx';
+import { Provider } from 'react-redux';
+import store from './redux/store.jsx';
+
+import { getCookie } from './Cookie.jsx';
+
+
+function getLoginStatus() {
+ const logged_in = getCookie('logged_in');
+ if (logged_in) {
+ return
+ }
+ return
+}
+
+const Admin = () => (
+
+ { getLoginStatus() }
+
+)
+
+export default Admin
\ No newline at end of file
diff --git a/client/app/components/App.jsx b/client/app/components/App.jsx
index edc567c..6f87df6 100644
--- a/client/app/components/App.jsx
+++ b/client/app/components/App.jsx
@@ -1,27 +1,12 @@
-/*
- ./app/components/App.jsx
-
-
-*/
-import React from 'react';
-import FetchData from './FetchData.js';
-import ListStrays from './ListStrays.jsx'
-import SearchRequest from './SearchRequest.jsx';
+import React, { Component } from "react";
+import Header from './Header.jsx';
+import Main from './Main.jsx';
-export default class App extends React.Component {
+const App = () => (
+
+
+
+
+)
- //
- //
Welcome to Seasoned
- //
- //
-
- //
- render() {
- return (
-
-
-
-
- );
- }
-}
\ No newline at end of file
+export default App
\ No newline at end of file
diff --git a/client/app/components/Cookie.jsx b/client/app/components/Cookie.jsx
new file mode 100644
index 0000000..8aef20f
--- /dev/null
+++ b/client/app/components/Cookie.jsx
@@ -0,0 +1,26 @@
+
+import React from 'react';
+
+
+export function getCookie(cname) {
+ var name = cname + "=";
+ var decodedCookie = decodeURIComponent(document.cookie);
+ var ca = decodedCookie.split(';');
+ for(var i = 0; i item.status === filter)
+ }
+
+ sortRequestList(requestList, sort_type, reversed) {
+ requestList.sort(function(a,b) {
+ if(a[sort_type] < b[sort_type]) return -1;
+ if(a[sort_type] > b[sort_type]) return 1;
+ return 0;
+ });
+
+ if (reversed)
+ requestList.reverse();
+ }
+
+ createHTMLElement(data, index) {
+ var posterPath = 'https://image.tmdb.org/t/p/w300' + data.image_path;
+
+ if (data.user_agent !== null) {
+ var user_agent = data.user_agent.split(" ");
+ var agent_shortened = user_agent[1].replace(/[\(\;]/g, '')
+ }
+
+ return (
+
+

+
+ Name: {data.name}
+ Year: {data.year}
+ Status: {data.status}
+ Address: {data.ip}
+ Requested Data: {data.requested_date}
+ Requested By: {data.requested_by}
+ Agent: {agent_shortened}
+
+
+ )
+ }
+
+ render() {
+ const {requestedElementsList, requestedElementsFilter, requestedElementsSort, props} = this.props;
+
+ var filteredRequestedList = this.filterRequestList(requestedElementsList, requestedElementsFilter)
+
+ this.sortRequestList(filteredRequestedList, requestedElementsSort.value, requestedElementsSort.reversed)
+
+ return (
+
+ {filteredRequestedList.map((requestItem, index) => this.createHTMLElement(requestItem, index))}
+
+ );
+ }
+}
+
+
+class FetchRequested extends React.Component {
+ constructor(props){
+ super(props)
+ this.state = {
+ requested_objects: [],
+ filter: 'all',
+ sort: {
+ value: 'requested_date',
+ reversed: false
+ },
+ }
+ }
+
+ componentDidMount(){
+ Promise.resolve()
+ fetch('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', {
+ method: 'GET',
+ headers: {
+ 'Content-type': 'application/json',
+ 'authorization': getCookie('token')
+ }
+ })
+ .then(response => {
+ if (response.status !== 200) {
+ console.log('error');
+ }
+
+ response.json()
+ .then(data => {
+ if (data.success === true) {
+ this.setState({
+ requested_objects: data.requestedItems
+ })
+ }
+ })
+ })
+ .catch(error => {
+ new Error(error);
+ })
+ }
+
+ changeFilter(filter) {
+ this.setState({
+ filter: filter
+ })
+ }
+
+ updateSort(sort=null, reverse=false) {
+ if (sort) {
+ this.setState({
+ sort: { value: sort, reversed: reverse }
+ })
+ }
+ else {
+ this.setState({
+ sort: { value: this.state.sort.value, reversed: reverse }
+ })
+ }
+ }
+
+
+
+ render(){
+ return(
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default FetchRequested;
\ No newline at end of file
diff --git a/client/app/components/Header.jsx b/client/app/components/Header.jsx
new file mode 100644
index 0000000..e068151
--- /dev/null
+++ b/client/app/components/Header.jsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { Link } from 'react-router-dom'
+
+// The Header creates links that can be used to navigate
+// between routes.
+const Header = () => (
+
+)
+
+export default Header
diff --git a/client/app/components/LoginForm/LoginForm.jsx b/client/app/components/LoginForm/LoginForm.jsx
new file mode 100644
index 0000000..f25ab83
--- /dev/null
+++ b/client/app/components/LoginForm/LoginForm.jsx
@@ -0,0 +1,66 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { login } from '../redux/reducer.jsx';
+
+class LoginForm extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {};
+ this.onSubmit = this.onSubmit.bind(this);
+ }
+
+ render() {
+ let {email, password} = this.state;
+ let {isLoginPending, isLoginSuccess, loginError} = this.props;
+ return (
+
+ )
+ }
+
+ onSubmit(e) {
+ e.preventDefault();
+ let { email, password } = this.state;
+ this.props.login(email, password);
+ this.setState({
+ email: '',
+ password: ''
+ });
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ isLoginPending: state.isLoginPending,
+ isLoginSuccess: state.isLoginSuccess,
+ loginError: state.loginError
+ };
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ login: (email, password) => dispatch(login(email, password))
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
\ No newline at end of file
diff --git a/client/app/components/Main.jsx b/client/app/components/Main.jsx
new file mode 100644
index 0000000..118bb50
--- /dev/null
+++ b/client/app/components/Main.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { HashRouter as Router, Route, Switch} from 'react-router-dom';
+import { createBrowserHistory } from 'history';
+
+import SearchRequest from './SearchRequest.jsx';
+import Admin from './Admin.jsx';
+import NotFound from './NotFound.js';
+
+export const history = createBrowserHistory();
+
+const Main = () => (
+
+
+
+
+
+
+
+)
+
+export default Main
diff --git a/client/app/components/MovieObject.jsx b/client/app/components/MovieObject.jsx
index 5b6f58a..e114900 100644
--- a/client/app/components/MovieObject.jsx
+++ b/client/app/components/MovieObject.jsx
@@ -34,7 +34,7 @@ class MovieObject {
notify.show(this.title + ' requested!', 'success', 3000);
}
- getElement() {
+ getElement(index) {
// TODO set the poster image async by updating the dom after this is returned
if (this.poster == null || this.poster == undefined) {
var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png'
@@ -60,7 +60,7 @@ class MovieObject {
return (
-
+
diff --git a/client/app/components/NotFound.js b/client/app/components/NotFound.js
new file mode 100644
index 0000000..5984e9b
--- /dev/null
+++ b/client/app/components/NotFound.js
@@ -0,0 +1,10 @@
+// components/NotFound.js
+import React from 'react';
+
+const NotFound = () =>
+
+
404 page not found
+
We are sorry but the page you are looking for does not exist.
+
+
+export default NotFound;
\ No newline at end of file
diff --git a/client/app/components/SearchRequest.jsx b/client/app/components/SearchRequest.jsx
index 1f488dc..c9f64ca 100644
--- a/client/app/components/SearchRequest.jsx
+++ b/client/app/components/SearchRequest.jsx
@@ -156,11 +156,11 @@ class SearchRequest extends React.Component {
.then(responseData => {
if (this.state.page === 1) {
this.setState({
- responseMovieList: responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem)),
+ responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
} else {
- let responseMovieObjects = responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem));
+ let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
this.setState({
responseMovieList: growingReponseMovieObjectList,
@@ -202,11 +202,11 @@ class SearchRequest extends React.Component {
.then(responseData => {
if (this.state.page === 1) {
this.setState({
- responseMovieList: responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem)),
+ responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
lastApiCallURI: uri // Save the value of the last sucessfull api call
})
} else {
- let responseMovieObjects = responseData.results.map(searchResultItem => this.createMovieObjects(searchResultItem));
+ let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
this.setState({
responseMovieList: growingReponseMovieObjectList,
@@ -288,9 +288,9 @@ class SearchRequest extends React.Component {
// When called passes the variable to MovieObject and calls it's interal function for
// generating the wanted HTML
- createMovieObjects(item) {
+ createMovieObjects(item, index) {
let movie = new MovieObject(item);
- return movie.getElement();
+ return movie.getElement(index);
}
toggleFilter(filterType) {
diff --git a/client/app/components/redux/reducer.jsx b/client/app/components/redux/reducer.jsx
new file mode 100644
index 0000000..18317d7
--- /dev/null
+++ b/client/app/components/redux/reducer.jsx
@@ -0,0 +1,108 @@
+
+import { setCookie } from '../Cookie.jsx';
+
+const SET_LOGIN_PENDING = 'SET_LOGIN_PENDING';
+const SET_LOGIN_SUCCESS = 'SET_LOGIN_SUCCESS';
+const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR';
+
+export function login(email, password) {
+ return dispatch => {
+ dispatch(setLoginPending(true));
+ dispatch(setLoginSuccess(false));
+ dispatch(setLoginError(null));
+
+ callLoginApi(email, password, error => {
+ dispatch(setLoginPending(false));
+ if (!error) {
+ dispatch(setLoginSuccess(true));
+ } else {
+ dispatch(setLoginError(error));
+ }
+ });
+ }
+}
+
+function setLoginPending(isLoginPending) {
+ return {
+ type: SET_LOGIN_PENDING,
+ isLoginPending
+ };
+}
+
+function setLoginSuccess(isLoginSuccess) {
+ return {
+ type: SET_LOGIN_SUCCESS,
+ isLoginSuccess
+ };
+}
+
+function setLoginError(loginError) {
+ return {
+ type: SET_LOGIN_ERROR,
+ loginError
+ }
+}
+
+
+function callLoginApi(email, password, callback) {
+
+ Promise.resolve()
+ fetch('https://apollo.kevinmidboe.com/api/v1/user/login', {
+ method: 'POST',
+ headers: {
+ 'Content-type': 'application/json'
+ },
+ body: JSON.stringify({
+ username: email,
+ password: password,
+ })
+ })
+ .then(response => {
+ switch (response.status) {
+ case 200:
+ response.json()
+ .then((data) => {
+ if (data.success === true) {
+ let token = data.token;
+ setCookie('token', token, 10);
+ setCookie('logged_in', true, 10);
+
+ window.location.reload();
+ }
+ return callback(null);
+ })
+
+ case 401:
+ return callback(new Error(response.statusText));
+ }
+ })
+ .catch(error => {
+ return callback(new Error('Invalid email and password'));
+ });
+}
+
+export default function reducer(state = {
+ isLoginSuccess: false,
+ isLoginPending: false,
+ loginError: null
+}, action) {
+ switch (action.type) {
+ case SET_LOGIN_PENDING:
+ return Object.assign({}, state, {
+ isLoginPending: action.isLoginPending
+ });
+
+ case SET_LOGIN_SUCCESS:
+ return Object.assign({}, state, {
+ isLoginSuccess: action.isLoginSuccess
+ });
+
+ case SET_LOGIN_ERROR:
+ return Object.assign({}, state, {
+ loginError: action.loginError
+ });
+
+ default:
+ return state;
+ }
+}
\ No newline at end of file
diff --git a/client/app/components/redux/store.jsx b/client/app/components/redux/store.jsx
new file mode 100644
index 0000000..6af9d92
--- /dev/null
+++ b/client/app/components/redux/store.jsx
@@ -0,0 +1,7 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import logger from 'redux-logger';
+import reducer from './reducer.jsx';
+
+const store = createStore(reducer, {}, applyMiddleware(thunk, logger));
+export default store;
\ No newline at end of file
diff --git a/client/app/components/styles/requestElementStyle.jsx b/client/app/components/styles/requestElementStyle.jsx
new file mode 100644
index 0000000..8fa0c15
--- /dev/null
+++ b/client/app/components/styles/requestElementStyle.jsx
@@ -0,0 +1,23 @@
+
+export default {
+ bodyDiv: {
+ display: 'flex',
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ flexFlow: 'row wrap',
+ },
+
+ wrappingDiv: {
+
+ },
+
+ requestPoster: {
+ height: '150px',
+ },
+
+ infoDiv: {
+ marginTop: 0,
+ marginLeft: '10px',
+ float: 'right',
+ },
+}
\ No newline at end of file
diff --git a/client/app/index.js b/client/app/index.js
index ecc68f9..214f861 100644
--- a/client/app/index.js
+++ b/client/app/index.js
@@ -2,14 +2,19 @@
* @Author: KevinMidboe
* @Date: 2017-06-01 21:08:55
* @Last Modified by: KevinMidboe
-* @Last Modified time: 2017-06-01 21:34:32
+* @Last Modified time: 2017-10-05 13:47:37
./client/index.js
which is the webpack entry file
*/
import React from 'react';
-import ReactDOM from 'react-dom';
+import { render } from 'react-dom';
+import { HashRouter } from 'react-router-dom';
import App from './components/App.jsx';
-ReactDOM.render(, document.getElementById('root'));
\ No newline at end of file
+render((
+
+
+
+), document.getElementById('root'));
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index e6fe23f..38c7c71 100644
--- a/client/package.json
+++ b/client/package.json
@@ -17,7 +17,13 @@
"react-dom": "^15.5.4",
"react-infinite-scroller": "^1.0.15",
"react-notify-toast": "^0.3.2",
+ "react-redux": "^5.0.6",
"react-responsive": "^1.3.4",
+ "react-router": "^4.2.0",
+ "react-router-dom": "^4.2.2",
+ "redux": "^3.7.2",
+ "redux-logger": "^3.0.6",
+ "redux-thunk": "^2.2.0",
"urijs": "^1.18.12",
"webfontloader": "^1.6.28",
"webpack": "^3.5.5",