|
@@ -1,14 +1,30 @@
|
|
|
import './App.css';
|
|
|
import * as action from './actions'
|
|
|
import * as reducer from './reducers'
|
|
|
+import * as Logcomp from './components/Login'
|
|
|
+import * as Sidebar from './components/Sidebar'
|
|
|
+import * as Page from './components/Page'
|
|
|
|
|
|
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 { Link, Route, Router, Switch } from 'react-router-dom';
|
|
|
import createHistory from 'history/createBrowserHistory'
|
|
|
|
|
|
+export function jwtDecode(token) {
|
|
|
+ try {
|
|
|
+ let decoded = token.split('.')
|
|
|
+ decoded = decoded[1]
|
|
|
+ decoded = atob(decoded)
|
|
|
+ decoded = JSON.parse(decoded)
|
|
|
+ return decoded
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export const getGQL = url =>
|
|
|
(query, variables = {}) =>
|
|
|
fetch(url, {
|
|
@@ -25,7 +41,7 @@ export const getGQL = url =>
|
|
|
return data.data[Object.keys(data.data)[0]]
|
|
|
})
|
|
|
|
|
|
-const history = createHistory()
|
|
|
+export const history = createHistory()
|
|
|
const backendURL = "http://player.asmer.fs.a-level.com.ua"
|
|
|
export const gql = getGQL(backendURL + '/graphql')
|
|
|
|
|
@@ -38,10 +54,7 @@ const store = createStore(
|
|
|
}
|
|
|
), applyMiddleware(thunk)
|
|
|
)
|
|
|
-
|
|
|
store.subscribe(() => console.log(store.getState()))
|
|
|
-
|
|
|
-
|
|
|
//works only once on start of page
|
|
|
if(store.getState().auth?.token) {
|
|
|
history.push('/player')
|
|
@@ -51,251 +64,77 @@ if(store.getState().auth?.token) {
|
|
|
history.push('/login')
|
|
|
}
|
|
|
|
|
|
-export function jwtDecode(token) {
|
|
|
- try {
|
|
|
- let decoded = token.split('.')
|
|
|
- decoded = decoded[1]
|
|
|
- decoded = atob(decoded)
|
|
|
- decoded = JSON.parse(decoded)
|
|
|
- return decoded
|
|
|
-
|
|
|
- } catch (e) {
|
|
|
- return;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const LoginForm = ({ loged, onLogin }) => {
|
|
|
- let [login, setLogin] = useState()
|
|
|
- let [password, setPassword] = useState()
|
|
|
- let [log, setLog] = useState()
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- setLog(loged)
|
|
|
- if (log?.payload && localStorage.authToken) history.push('/player')
|
|
|
- }, [loged, log])
|
|
|
-
|
|
|
- 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 />
|
|
|
- <small style={{ color: 'red' }}>{loged.status === 'REJECTED' || (loged.status === 'RESOLVED' && !loged.payload) ? 'invalid login or password' : ''}</small>
|
|
|
- <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(state => ({ loged: state.promise.login || {} }), { onLogin: action.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: action.actionRegister })(RegisterForm)
|
|
|
-
|
|
|
-const PlaylistAdd = ({addPlaylist}) => {
|
|
|
- let [clicked, setClicked] = useState(false)
|
|
|
- let [name, setName] = useState()
|
|
|
- return (
|
|
|
- <div>
|
|
|
- {
|
|
|
- !clicked?
|
|
|
- <button
|
|
|
- style={{ border: '1px solid black', backgroundColor: 'mediumseagreen',width : '95%',padding: '5px', margin: '5px' }}
|
|
|
- onClick={() => setClicked(true)}
|
|
|
- >NEW PLAYLIST</button>
|
|
|
- :
|
|
|
- <div style={{width : '95%', margin:'0 auto'}}>
|
|
|
- <input
|
|
|
- style={{width:'72%', padding:'5px'}}
|
|
|
- placeholder='Playlist name'
|
|
|
- value={name}
|
|
|
- onChange={(e) => setName(e.target.value)}
|
|
|
- />
|
|
|
-
|
|
|
- <button
|
|
|
- disabled={!name}
|
|
|
- style={{padding:'5px', backgroundColor:'mediumseagreen'}}
|
|
|
- onClick={() => {addPlaylist(name); setClicked(false); setName('');}}
|
|
|
- >+</button>
|
|
|
-
|
|
|
- <button
|
|
|
- style={{padding:'5px', backgroundColor:'red'}}
|
|
|
- onClick={() => {setClicked(false); setName('')}}
|
|
|
- >X</button>
|
|
|
- </div>
|
|
|
- }
|
|
|
- </div>
|
|
|
- )
|
|
|
-}
|
|
|
-const PlaylistAddConnect = connect(null, {addPlaylist: action.actionAddPlaylist})(PlaylistAdd)
|
|
|
-
|
|
|
-const Playlists = ({playlists}) => {
|
|
|
- return (
|
|
|
- <div style={{backgroundColor: 'lightcyan'}}>
|
|
|
- <PlaylistAddConnect />
|
|
|
- {
|
|
|
- playlists?.payload? playlists.payload.map(item => {
|
|
|
- return (
|
|
|
- <Link
|
|
|
- style={{display:'block', backgroundColor: 'darkcyan', color: 'cyan', margin: '5px', padding:'5px'}}
|
|
|
- to={`${history.location.pathname}/playlist/:${item._id}`}
|
|
|
- >{item.name}</Link>
|
|
|
- )
|
|
|
- }) : ''
|
|
|
- }
|
|
|
- </div>
|
|
|
- )
|
|
|
-}
|
|
|
-const PlaylistsConnect = connect(state => ({playlists: state.promise.userPlaylists || {}}))(Playlists)
|
|
|
-
|
|
|
-const Track = ({track:{url, originalFileName, id3:{title, artist, album}}}) =>
|
|
|
- <li style={{border: '1px solid black', display:'flex', alignItems:'center'}}>
|
|
|
- <div style={{marginRight:'2%'}}>
|
|
|
- <button style={{padding: '10px', margin:'2px'}}> {`>`} </button>
|
|
|
- <button style={{padding: '10px', margin:'2px'}}>| |</button>
|
|
|
- <br/>
|
|
|
- <button style={{padding: '10px', margin:'2px'}}>Add to...</button>
|
|
|
- </div>
|
|
|
- <div style={{textAlign: 'left'}}>
|
|
|
- <h5>{artist || 'Artist: unknown'}</h5>
|
|
|
- <h6>{album || 'Album: unknown'}</h6>
|
|
|
- <h5>{title || originalFileName}</h5>
|
|
|
- <p>{url}</p>
|
|
|
- </div>
|
|
|
- </li>
|
|
|
-
|
|
|
-const Playlist = ({playlist}) =>
|
|
|
- <>
|
|
|
- <h2>{playlist[0]?.name || 'Playlist'}</h2>
|
|
|
- <ul>
|
|
|
- {(playlist[0]?.tracks || []).map(track => <Track track={track}/>)}
|
|
|
- </ul>
|
|
|
- </>
|
|
|
-
|
|
|
-const PlaylistConnect = connect(state => ({playlist: state.promise.playlistTracks?.payload || []}))(Playlist)
|
|
|
-
|
|
|
-const PlaylistPage = ({match: {params: {_id}}, getTracks}) => {
|
|
|
- useEffect(() => {
|
|
|
- console.log('BANG', _id)
|
|
|
- getTracks(_id.substring(1))
|
|
|
- //getTracks()
|
|
|
- }, [_id])
|
|
|
- return(<PlaylistConnect />)
|
|
|
-}
|
|
|
-const PlaylistPageConnect = connect(null, {getTracks: action.actionGetPlaylistById})(PlaylistPage)
|
|
|
-
|
|
|
-
|
|
|
-const UserTracks = ({user, tracks}) =>
|
|
|
- <>
|
|
|
- <h2>{ user.login || 'My' } tracks:</h2>
|
|
|
- <ul>
|
|
|
- {(tracks || []).map(track => <Track track={track}/>)}
|
|
|
- </ul>
|
|
|
- </>
|
|
|
-
|
|
|
-const UserTracksConnect = connect(state => ({
|
|
|
- tracks: state.promise.userTracks?.payload || [],
|
|
|
- user: state.promise.userData?.payload || {}
|
|
|
- })
|
|
|
-)(UserTracks)
|
|
|
-
|
|
|
-const UserTracksPage = ({match: {params: {_id}}, getUserTracks}) => {
|
|
|
- useEffect(() => {
|
|
|
- getUserTracks()
|
|
|
- },[_id])
|
|
|
- return(<UserTracksConnect/>)
|
|
|
-}
|
|
|
-const UserTracksPageConnect = connect(null, {getUserTracks: action.actionGetUserTracks})(UserTracksPage)
|
|
|
-
|
|
|
-const Player = ({ user, playlists, onLogout }) => {
|
|
|
+const ProfileWindow = ({user, onLogout}) => {
|
|
|
let [userInfo, setUserInfo] = useState(user.payload)
|
|
|
- let [userPlaylists, setPlaylists] = useState(user.payload)
|
|
|
|
|
|
useEffect(()=> {
|
|
|
setUserInfo(user.payload)
|
|
|
- setPlaylists(playlists.payload)
|
|
|
- console.log(userPlaylists)
|
|
|
- },[user, playlists, userInfo, userPlaylists])
|
|
|
-
|
|
|
- return (
|
|
|
- <>
|
|
|
- <header>Player</header>
|
|
|
- <div style={{ display: 'flex' }}>
|
|
|
- <aside style={{ border: '1px solid black', width: '30%' }}>
|
|
|
- <div
|
|
|
- style={{ border: '1px solid black', backgroundColor: 'red', color: 'white' }}
|
|
|
- onClick={() => { onLogout(); history.push('/login') }}
|
|
|
- >log-out[X]</div>
|
|
|
- {/* profile window */}
|
|
|
- <div style={{border:'1px solid chartreuse'}}>
|
|
|
- <h3>{userInfo?.login || 'user'}</h3>
|
|
|
- <img
|
|
|
- width={100}
|
|
|
- height={100}
|
|
|
- style={{ border: '1px solid black', display:'block', margin:'5% auto', marginBottom:'2px'}}
|
|
|
- src={ userInfo?.avatar?.url ? backendURL + '/' + userInfo?.avatar?.url : ''}
|
|
|
- alt='avatar'
|
|
|
- />
|
|
|
- <small>change avavtar</small>
|
|
|
- </div>
|
|
|
- <Link
|
|
|
- style={{display:'block', backgroundColor: 'purple', color: 'white', margin: '5px', padding:'5px'}}
|
|
|
- to={`/player/tracks/:${userInfo?._id}`}
|
|
|
- >My tracks</Link>
|
|
|
- <PlaylistsConnect />
|
|
|
- </aside>
|
|
|
- <main style={{ border: '1px solid black', width: '80%' }}>
|
|
|
- <Switch>
|
|
|
- <Route path="/player/playlist/:_id" component={PlaylistPageConnect} exact/>
|
|
|
- <Route path="/player/tracks/:_id" component={UserTracksPageConnect} exact/>
|
|
|
- </Switch>
|
|
|
- </main>
|
|
|
+ },[user, userInfo])
|
|
|
+
|
|
|
+ return(
|
|
|
+ <section>
|
|
|
+ <div
|
|
|
+ style={{ border: '1px solid black', backgroundColor: 'red', color: 'white' }}
|
|
|
+ onClick={() => { onLogout(); history.push('/login') }}
|
|
|
+ >log-out[X]</div>
|
|
|
+ <div style={{border:'1px solid chartreuse'}}>
|
|
|
+ <h3>{userInfo?.login || 'user'}</h3>
|
|
|
+ <img
|
|
|
+ width={100}
|
|
|
+ height={100}
|
|
|
+ style={{ border: '1px solid black', display:'block', margin:'5% auto', marginBottom:'2px'}}
|
|
|
+ src={ userInfo?.avatar?.url ? backendURL + '/' + userInfo?.avatar?.url : ''}
|
|
|
+ alt='avatar'
|
|
|
+ />
|
|
|
+ <small>change avavtar</small>
|
|
|
</div>
|
|
|
- <footer> back stop forw</footer>
|
|
|
- </>
|
|
|
- )
|
|
|
+ <Link
|
|
|
+ to={`/player/tracks/:${userInfo?._id}`}
|
|
|
+ style={{
|
|
|
+ display:'block',
|
|
|
+ backgroundColor: 'purple',
|
|
|
+ color: 'white',
|
|
|
+ margin: '5px',
|
|
|
+ padding:'5px'
|
|
|
+ }}
|
|
|
+ >My tracks</Link>
|
|
|
+ </section>
|
|
|
+ )
|
|
|
}
|
|
|
+const ProfileWindowConnect = connect(state => ({ user: state.promise.userData || {} }),{ onLogout: action.actionAuthLogout })(ProfileWindow)
|
|
|
+
|
|
|
|
|
|
-const PlayerConnect = connect(
|
|
|
- state => ({
|
|
|
- user: state.promise.userData || {},
|
|
|
- playlists: state.promise.userPlaylists || {}
|
|
|
- }),
|
|
|
- {
|
|
|
- onLogout: action.actionAuthLogout,
|
|
|
- }
|
|
|
-)(Player)
|
|
|
+const Player = () =>
|
|
|
+ <>
|
|
|
+ <header>Player</header>
|
|
|
+ <div style={{ display: 'flex' }}>
|
|
|
+ <aside style={{ border: '1px solid black', width: '30%' }}>
|
|
|
+ <ProfileWindowConnect />
|
|
|
+ <Sidebar.PlaylistsConnect />
|
|
|
+ </aside>
|
|
|
+ <main style={{ border: '1px solid black', width: '80%' }}>
|
|
|
+ <Switch>
|
|
|
+ <Route path="/player/playlist/:_id" component={Page.PlaylistPageConnect} exact/>
|
|
|
+ <Route path="/player/tracks/:_id" component={Page.UserTracksPageConnect} exact/>
|
|
|
+ <>
|
|
|
+ <h2>Welcome to online Player!</h2>
|
|
|
+ <div style={{width:'50%',margin: '0 auto'}}>
|
|
|
+ <ul style={{textAlign:'start'}}>
|
|
|
+ <li><strong>To create playlist: </strong>click "NEW PLAYLIST"</li>
|
|
|
+ <li><strong>To upload track: </strong>drag and drop it to playlist area</li>
|
|
|
+ <li><strong>To see list of all your tracks: </strong>click "My tracks"</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ </Switch>
|
|
|
+ </main>
|
|
|
+ </div>
|
|
|
+ <footer> back stop forw</footer>
|
|
|
+ </>
|
|
|
+
|
|
|
+// const PlayerConnect = connect(
|
|
|
+// state => ({ user: state.promise.userData || {} }),{ onLogout: action.actionAuthLogout}
|
|
|
+// )(Player)
|
|
|
|
|
|
|
|
|
function App() {
|
|
@@ -304,9 +143,9 @@ function App() {
|
|
|
<Provider store={store}>
|
|
|
<div className="App">
|
|
|
<Switch>
|
|
|
- <Route path="/login" component={LoginFormConnect} exact />
|
|
|
- <Route path="/registration" component={RegisterFormConnect} exact />
|
|
|
- <Route path='/player' component={PlayerConnect} />
|
|
|
+ <Route path="/login" component={Logcomp.LoginFormConnect} exact />
|
|
|
+ <Route path="/registration" component={Logcomp.RegisterFormConnect} exact />
|
|
|
+ <Route path='/player' component={Player} />
|
|
|
</Switch>
|
|
|
</div>
|
|
|
</Provider>
|