App.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import './App.css';
  2. import * as action from './actions'
  3. import * as reducer from './reducers'
  4. import thunk from 'redux-thunk';
  5. import { useEffect, useState } from 'react';
  6. import { createStore, combineReducers, applyMiddleware } from 'redux';
  7. import { Provider, connect } from 'react-redux';
  8. import { Link, Route, Router, Switch, Redirect } from 'react-router-dom';
  9. import createHistory from 'history/createBrowserHistory'
  10. export const getGQL = url =>
  11. (query, variables = {}) =>
  12. fetch(url, {
  13. method: 'POST',
  14. headers: {
  15. "Content-Type": "application/json",
  16. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
  17. },
  18. body: JSON.stringify({ query, variables })
  19. })
  20. .then(res => res.json())
  21. .then(data => {
  22. if (data.errors && !data.data) throw new Error(JSON.stringify(data.errors))
  23. return data.data[Object.keys(data.data)[0]]
  24. })
  25. const history = createHistory()
  26. const backendURL = "http://player.asmer.fs.a-level.com.ua"
  27. export const gql = getGQL(backendURL + '/graphql')
  28. const store = createStore(
  29. combineReducers(
  30. {
  31. promise: reducer.promiseReducer,
  32. auth: reducer.authReducer,
  33. //local: localStoreReducer(promiseReducer, 'locale')
  34. }
  35. ), applyMiddleware(thunk)
  36. )
  37. store.subscribe(() => console.log(store.getState()))
  38. //works only once on start of page
  39. if(store.getState().auth?.token) {
  40. history.push('/player')
  41. store.dispatch(action.actionGetUserData())
  42. store.dispatch(action.actionGetUserPlaylists())
  43. } else {
  44. history.push('/login')
  45. }
  46. export function jwtDecode(token) {
  47. try {
  48. let decoded = token.split('.')
  49. decoded = decoded[1]
  50. decoded = atob(decoded)
  51. decoded = JSON.parse(decoded)
  52. return decoded
  53. } catch (e) {
  54. return;
  55. }
  56. }
  57. const LoginForm = ({ loged, onLogin }) => {
  58. let [login, setLogin] = useState()
  59. let [password, setPassword] = useState()
  60. let [log, setLog] = useState()
  61. useEffect(() => {
  62. setLog(loged)
  63. if (log?.payload && localStorage.authToken) history.push('/player')
  64. }, [loged, log])
  65. return (
  66. <>
  67. <h1>Web-player</h1>
  68. <div>
  69. <h2>Log-in</h2>
  70. <input type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)} />
  71. <br />
  72. <input type="password" placeholder='Password' onChange={(e) => setPassword(e.target.value)} />
  73. <br />
  74. <small style={{ color: 'red' }}>{loged.status === 'REJECTED' || (loged.status === 'RESOLVED' && !loged.payload) ? 'invalid login or password' : ''}</small>
  75. <br />
  76. <button
  77. disabled={!password || !login}
  78. onClick={() => { onLogin(login, password) }}
  79. >Login</button>
  80. <p>- OR -</p>
  81. <Link to="/registration">Register new user</Link>
  82. </div>
  83. </>
  84. )
  85. }
  86. const LoginFormConnect = connect(state => ({ loged: state.promise.login || {} }), { onLogin: action.actionFullLogin })(LoginForm)
  87. const RegisterForm = ({ onRegister }) => {
  88. let [login, setLogin] = useState()
  89. let [password, setPassword] = useState()
  90. let [password2, setPassword2] = useState()
  91. return (
  92. <>
  93. <h1>Web-player</h1>
  94. <div>
  95. <h2>Registration</h2>
  96. <input type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)} />
  97. <br />
  98. <input type="password" placeholder='Password' onChange={(e) => setPassword(e.target.value)} />
  99. <br />
  100. <input disabled={!password} type="password" placeholder='Repeat Password' onChange={(e) => setPassword2(e.target.value)} />
  101. <br />
  102. <small style={{ color: 'red' }}>{password2 && password2 !== password ? 'Passwords do not match' : ''}</small>
  103. <br />
  104. <button disabled={!password || !login || password2 !== password} onClick={() => onRegister(login, password)}>Register</button>
  105. <br />
  106. <Link to="/login">Back to log-in page</Link>
  107. </div>
  108. </>
  109. )
  110. }
  111. const RegisterFormConnect = connect(null, { onRegister: action.actionRegister })(RegisterForm)
  112. const PlaylistAdd = ({addPlaylist}) => {
  113. let [clicked, setClicked] = useState(false)
  114. let [name, setName] = useState()
  115. return (
  116. <div>
  117. {
  118. !clicked?
  119. <button
  120. style={{ border: '1px solid black', backgroundColor: 'mediumseagreen',width : '95%',padding: '5px', margin: '5px' }}
  121. onClick={() => setClicked(true)}
  122. >NEW PLAYLIST</button>
  123. :
  124. <div style={{width : '95%', margin:'0 auto'}}>
  125. <input
  126. style={{width:'72%', padding:'5px'}}
  127. placeholder='Playlist name'
  128. value={name}
  129. onChange={(e) => setName(e.target.value)}
  130. />
  131. <button
  132. disabled={!name}
  133. style={{padding:'5px', backgroundColor:'mediumseagreen'}}
  134. onClick={() => {addPlaylist(name); setClicked(false); setName('');}}
  135. >+</button>
  136. <button
  137. style={{padding:'5px', backgroundColor:'red'}}
  138. onClick={() => {setClicked(false); setName('')}}
  139. >X</button>
  140. </div>
  141. }
  142. </div>
  143. )
  144. }
  145. const PlaylistAddConnect = connect(null, {addPlaylist: action.actionAddPlaylist})(PlaylistAdd)
  146. const Playlists = ({playlists}) => {
  147. return (
  148. <div style={{backgroundColor: 'lightcyan'}}>
  149. <PlaylistAddConnect />
  150. {
  151. playlists?.payload? playlists.payload.map(item => {
  152. return (
  153. <Link
  154. style={{display:'block', backgroundColor: 'darkcyan', color: 'cyan', margin: '5px', padding:'5px'}}
  155. to={`${history.location.pathname}/playlist/:${item._id}`}
  156. >{item.name}</Link>
  157. )
  158. }) : ''
  159. }
  160. </div>
  161. )
  162. }
  163. const PlaylistsConnect = connect(state => ({playlists: state.promise.userPlaylists || {}}))(Playlists)
  164. const Track = ({track:{url, originalFileName, id3:{title, artist, album}}}) =>
  165. <li style={{border: '1px solid black', display:'flex', alignItems:'center'}}>
  166. <div style={{marginRight:'2%'}}>
  167. <button style={{padding: '10px', margin:'2px'}}> {`>`} </button>
  168. <button style={{padding: '10px', margin:'2px'}}>| |</button>
  169. <br/>
  170. <button style={{padding: '10px', margin:'2px'}}>Add to...</button>
  171. </div>
  172. <div style={{textAlign: 'left'}}>
  173. <h5>{artist || 'Artist: unknown'}</h5>
  174. <h6>{album || 'Album: unknown'}</h6>
  175. <h5>{title || originalFileName}</h5>
  176. <p>{url}</p>
  177. </div>
  178. </li>
  179. const Playlist = ({playlist}) =>
  180. <>
  181. <h2>{playlist[0]?.name || 'Playlist'}</h2>
  182. <ul>
  183. {(playlist[0]?.tracks || []).map(track => <Track track={track}/>)}
  184. </ul>
  185. </>
  186. const PlaylistConnect = connect(state => ({playlist: state.promise.playlistTracks?.payload || []}))(Playlist)
  187. const PlaylistPage = ({match: {params: {_id}}, getTracks}) => {
  188. useEffect(() => {
  189. console.log('BANG', _id)
  190. getTracks(_id.substring(1))
  191. //getTracks()
  192. }, [_id])
  193. return(<PlaylistConnect />)
  194. }
  195. const PlaylistPageConnect = connect(null, {getTracks: action.actionGetPlaylistById})(PlaylistPage)
  196. const UserTracks = ({user, tracks}) =>
  197. <>
  198. <h2>{ user.login || 'My' } tracks:</h2>
  199. <ul>
  200. {(tracks || []).map(track => <Track track={track}/>)}
  201. </ul>
  202. </>
  203. const UserTracksConnect = connect(state => ({
  204. tracks: state.promise.userTracks?.payload || [],
  205. user: state.promise.userData?.payload || {}
  206. })
  207. )(UserTracks)
  208. const UserTracksPage = ({match: {params: {_id}}, getUserTracks}) => {
  209. useEffect(() => {
  210. getUserTracks()
  211. },[_id])
  212. return(<UserTracksConnect/>)
  213. }
  214. const UserTracksPageConnect = connect(null, {getUserTracks: action.actionGetUserTracks})(UserTracksPage)
  215. const Player = ({ user, playlists, onLogout }) => {
  216. let [userInfo, setUserInfo] = useState(user.payload)
  217. let [userPlaylists, setPlaylists] = useState(user.payload)
  218. useEffect(()=> {
  219. setUserInfo(user.payload)
  220. setPlaylists(playlists.payload)
  221. console.log(userPlaylists)
  222. },[user, playlists, userInfo, userPlaylists])
  223. return (
  224. <>
  225. <header>Player</header>
  226. <div style={{ display: 'flex' }}>
  227. <aside style={{ border: '1px solid black', width: '30%' }}>
  228. <div
  229. style={{ border: '1px solid black', backgroundColor: 'red', color: 'white' }}
  230. onClick={() => { onLogout(); history.push('/login') }}
  231. >log-out[X]</div>
  232. {/* profile window */}
  233. <div style={{border:'1px solid chartreuse'}}>
  234. <h3>{userInfo?.login || 'user'}</h3>
  235. <img
  236. width={100}
  237. height={100}
  238. style={{ border: '1px solid black', display:'block', margin:'5% auto', marginBottom:'2px'}}
  239. src={ userInfo?.avatar?.url ? backendURL + '/' + userInfo?.avatar?.url : ''}
  240. alt='avatar'
  241. />
  242. <small>change avavtar</small>
  243. </div>
  244. <Link
  245. style={{display:'block', backgroundColor: 'purple', color: 'white', margin: '5px', padding:'5px'}}
  246. to={`/player/tracks/:${userInfo?._id}`}
  247. >My tracks</Link>
  248. <PlaylistsConnect />
  249. </aside>
  250. <main style={{ border: '1px solid black', width: '80%' }}>
  251. <Switch>
  252. <Route path="/player/playlist/:_id" component={PlaylistPageConnect} exact/>
  253. <Route path="/player/tracks/:_id" component={UserTracksPageConnect} exact/>
  254. </Switch>
  255. </main>
  256. </div>
  257. <footer> back stop forw</footer>
  258. </>
  259. )
  260. }
  261. const PlayerConnect = connect(
  262. state => ({
  263. user: state.promise.userData || {},
  264. playlists: state.promise.userPlaylists || {}
  265. }),
  266. {
  267. onLogout: action.actionAuthLogout,
  268. }
  269. )(Player)
  270. function App() {
  271. return (
  272. <Router history={history}>
  273. <Provider store={store}>
  274. <div className="App">
  275. <Switch>
  276. <Route path="/login" component={LoginFormConnect} exact />
  277. <Route path="/registration" component={RegisterFormConnect} exact />
  278. <Route path='/player' component={PlayerConnect} />
  279. </Switch>
  280. </div>
  281. </Provider>
  282. </Router>
  283. );
  284. }
  285. export default App;