|
@@ -1,44 +1,202 @@
|
|
|
import './App.css';
|
|
|
+import thunk from 'redux-thunk';
|
|
|
+import { useEffect, useState } from 'react';
|
|
|
+import {createStore, combineReducers, applyMiddleware} from 'redux';
|
|
|
+import {Provider, connect} from 'react-redux';
|
|
|
+import { Link, Route, Router, Switch, Redirect } from 'react-router-dom';
|
|
|
+import createHistory from 'history/createBrowserHistory'
|
|
|
|
|
|
-const LoginForm = () =>
|
|
|
- <>
|
|
|
- <h1>Web-player</h1>
|
|
|
- <div>
|
|
|
- <h2>Log-in</h2>
|
|
|
- <input type="text" placeholder='Login'/>
|
|
|
- <br />
|
|
|
- <input type="password" placeholder='Password'/>
|
|
|
- <br />
|
|
|
- <button>Login</button>
|
|
|
- <p>- OR -</p>
|
|
|
- <a href='#'>Register new user</a>
|
|
|
- </div>
|
|
|
- </>
|
|
|
+const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name})
|
|
|
+const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
|
|
|
+const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
|
|
|
+
|
|
|
+const actionPromise = (name, promise) =>
|
|
|
+ async dispatch => {
|
|
|
+ dispatch(actionPending(name))
|
|
|
+ try{
|
|
|
+ let payload = await promise
|
|
|
+ dispatch(actionResolved(name, payload))
|
|
|
+ return payload
|
|
|
+ }
|
|
|
+ catch(error){
|
|
|
+ dispatch(actionRejected(name, error))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+const getGQL = url =>
|
|
|
+ (query, variables = {}) =>
|
|
|
+ fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } :
|
|
|
+ {})
|
|
|
+ },
|
|
|
+ body: JSON.stringify({ query, variables })
|
|
|
+ })
|
|
|
+ .then(res => res.json())
|
|
|
+ .then(data => {
|
|
|
+ if (data.errors && !data.data)
|
|
|
+ throw new Error(JSON.stringify(data.errors))
|
|
|
+ return data.data[Object.keys(data.data)[0]]
|
|
|
+ })
|
|
|
+
|
|
|
+const backendURL = "http://player.asmer.fs.a-level.com.ua"
|
|
|
+const gql = getGQL(backendURL + '/graphql')
|
|
|
+
|
|
|
+function jwtDecode(token) {
|
|
|
+ try {
|
|
|
+ let decoded = token.split('.')
|
|
|
+ decoded = decoded[1]
|
|
|
+ decoded = atob(decoded)
|
|
|
+ decoded = JSON.parse(decoded)
|
|
|
+ return decoded
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function authReducer(state, {type, token}){
|
|
|
+ if (!state) {
|
|
|
+ if (localStorage.authToken) {
|
|
|
+ type = 'AUTH_LOGIN'
|
|
|
+ token = localStorage.authToken
|
|
|
+ } else {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'AUTH_LOGIN') {
|
|
|
+ let auth = jwtDecode(token)
|
|
|
+ if (auth) {
|
|
|
+ localStorage.authToken = token
|
|
|
+ return { token, payload: auth }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'AUTH_LOGOUT') {
|
|
|
+ localStorage.authToken = ''
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+function promiseReducer(state={}, {type, name, status, payload, error}){
|
|
|
+ if (type === 'PROMISE'){
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ [name]:{status, payload, error}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+const actionAuthLogin = (token) => ({ type: 'AUTH_LOGIN', token })
|
|
|
+const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
|
|
|
|
|
|
-const RegisterForm = () =>
|
|
|
- <>
|
|
|
- <h1>Web-player</h1>
|
|
|
- <div>
|
|
|
- <h2>Registration</h2>
|
|
|
- <input type="text" placeholder='Login'/>
|
|
|
- <br />
|
|
|
- <input type="text" placeholder='Nickname'/>
|
|
|
- <br />
|
|
|
- <input type="password" placeholder='Password'/>
|
|
|
- <br />
|
|
|
- <button>Register</button>
|
|
|
- <br />
|
|
|
- <a href='#'>Back to log-in</a>
|
|
|
- </div>
|
|
|
+const actionLogin = (login, password) =>
|
|
|
+ actionPromise('login', gql(`
|
|
|
+ query log($login:String!, $password:String!) {
|
|
|
+ login(login: $login, password: $password)
|
|
|
+ }`, { login, password }))
|
|
|
+
|
|
|
+const actionFullLogin = (login = 'tst', password = '123') =>
|
|
|
+ async dispatch => {
|
|
|
+ let token = await dispatch(actionLogin(login, password))
|
|
|
+ if (token) {
|
|
|
+ dispatch(actionAuthLogin(token))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+const actionRegister = (login, password) =>
|
|
|
+ actionPromise('registration', gql(`
|
|
|
+ mutation register($login:String!, $password:String!) {
|
|
|
+ createUser(login: $login, password: $password) {
|
|
|
+ login, _id
|
|
|
+ }
|
|
|
+ }
|
|
|
+ `, { login, password }))
|
|
|
+
|
|
|
+const actionFullRegister = (login = 'tst', password = '123') =>
|
|
|
+ async dispatch => {
|
|
|
+ await dispatch(actionRegister(login, password))
|
|
|
+ await dispatch(actionFullLogin(login, password))
|
|
|
+ }
|
|
|
+
|
|
|
+const store = createStore(
|
|
|
+ combineReducers(
|
|
|
+ {
|
|
|
+ promise: promiseReducer,
|
|
|
+ auth: authReducer
|
|
|
+ }
|
|
|
+ ), applyMiddleware(thunk)
|
|
|
+)
|
|
|
+store.subscribe(() => console.log(store.getState()))
|
|
|
+
|
|
|
+const LoginForm = ({onLogin}) => {
|
|
|
+ let [login, setLogin] = useState()
|
|
|
+ let [password, setPassword] = useState()
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <h1>Web-player</h1>
|
|
|
+ <div>
|
|
|
+ <h2>Log-in</h2>
|
|
|
+ <input type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)}/>
|
|
|
+ <br />
|
|
|
+ <input type="password" placeholder='Password' onChange={(e) => setPassword(e.target.value)}/>
|
|
|
+ <br />
|
|
|
+ <button disabled={!password || !login} onClick={() => onLogin(login, password)}>Login</button>
|
|
|
+ <p>- OR -</p>
|
|
|
+ <Link to="/registration">Register new user</Link>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const LoginFormConnect = connect(null,{onLogin: actionFullLogin})(LoginForm)
|
|
|
+
|
|
|
+const RegisterForm = ({onRegister}) => {
|
|
|
+ let [login, setLogin] = useState()
|
|
|
+ let [password, setPassword] = useState()
|
|
|
+ let [password2, setPassword2] = useState()
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <h1>Web-player</h1>
|
|
|
+ <div>
|
|
|
+ <h2>Registration</h2>
|
|
|
+ <input type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)}/>
|
|
|
+ <br />
|
|
|
+ <input type="password" placeholder='Password'onChange={(e) => setPassword(e.target.value)}/>
|
|
|
+ <br />
|
|
|
+ <input disabled={!password} type="password" placeholder='Repeat Password' onChange={(e) => setPassword2(e.target.value)}/>
|
|
|
+ <br />
|
|
|
+ <small style={{color: 'red'}}>{password2 && password2 !== password? 'Passwords do not match' : ''}</small>
|
|
|
+ <br />
|
|
|
+ <button disabled={!password || !login || password2 !== password } onClick={() => onRegister(login, password)}>Register</button>
|
|
|
+ <br />
|
|
|
+ <Link to="/login">Back to log-in page</Link>
|
|
|
+ </div>
|
|
|
</>
|
|
|
+ )
|
|
|
+}
|
|
|
+const RegisterFormConnect = connect(null,{onRegister: actionFullRegister})(RegisterForm)
|
|
|
|
|
|
+const history = createHistory()
|
|
|
|
|
|
function App() {
|
|
|
return (
|
|
|
- <div className="App">
|
|
|
- <LoginForm />
|
|
|
- <RegisterForm />
|
|
|
- </div>
|
|
|
+ <Router history={history}>
|
|
|
+ <Provider store={store}>
|
|
|
+ <div className="App">
|
|
|
+ <Switch>
|
|
|
+ <Route path="/login" component={LoginFormConnect} exact/>
|
|
|
+ <Route path="/registration" component={RegisterFormConnect} exact/>
|
|
|
+ </Switch>
|
|
|
+ </div>
|
|
|
+ </Provider>
|
|
|
+ </Router>
|
|
|
);
|
|
|
}
|
|
|
|