Browse Source

Merge branch 'dev' of Sergei-Levshnia/player-project into master

Sergei-Levshnia 2 năm trước cách đây
mục cha
commit
89d90a133e

+ 83 - 6
package-lock.json

@@ -8,6 +8,9 @@
       "name": "player-project",
       "version": "0.1.0",
       "dependencies": {
+        "@fortawesome/fontawesome-svg-core": "^1.2.36",
+        "@fortawesome/free-solid-svg-icons": "^5.15.4",
+        "@fortawesome/react-fontawesome": "^0.1.16",
         "@testing-library/jest-dom": "^5.16.1",
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
@@ -1910,6 +1913,51 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@fortawesome/fontawesome-common-types": {
+      "version": "0.2.36",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
+      "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==",
+      "hasInstallScript": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@fortawesome/fontawesome-svg-core": {
+      "version": "1.2.36",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
+      "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "@fortawesome/fontawesome-common-types": "^0.2.36"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@fortawesome/free-solid-svg-icons": {
+      "version": "5.15.4",
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
+      "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "@fortawesome/fontawesome-common-types": "^0.2.36"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@fortawesome/react-fontawesome": {
+      "version": "0.1.16",
+      "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.16.tgz",
+      "integrity": "sha512-aLmzDwC9rEOAJv2UJdMns89VZR5Ry4IHu5dQQh24Z/lWKEm44lfQr1UNalZlkUaQN8d155tNh+CS7ntntj1VMA==",
+      "dependencies": {
+        "prop-types": "^15.7.2"
+      },
+      "peerDependencies": {
+        "@fortawesome/fontawesome-svg-core": "~1 || >=1.3.0-beta1",
+        "react": ">=16.x"
+      }
+    },
     "node_modules/@gar/promisify": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
@@ -11729,9 +11777,9 @@
       "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
     },
     "node_modules/nanoid": {
-      "version": "3.1.30",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
-      "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
+      "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
       "bin": {
         "nanoid": "bin/nanoid.cjs"
       },
@@ -18797,6 +18845,35 @@
         }
       }
     },
+    "@fortawesome/fontawesome-common-types": {
+      "version": "0.2.36",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
+      "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
+    },
+    "@fortawesome/fontawesome-svg-core": {
+      "version": "1.2.36",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz",
+      "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==",
+      "requires": {
+        "@fortawesome/fontawesome-common-types": "^0.2.36"
+      }
+    },
+    "@fortawesome/free-solid-svg-icons": {
+      "version": "5.15.4",
+      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
+      "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+      "requires": {
+        "@fortawesome/fontawesome-common-types": "^0.2.36"
+      }
+    },
+    "@fortawesome/react-fontawesome": {
+      "version": "0.1.16",
+      "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.16.tgz",
+      "integrity": "sha512-aLmzDwC9rEOAJv2UJdMns89VZR5Ry4IHu5dQQh24Z/lWKEm44lfQr1UNalZlkUaQN8d155tNh+CS7ntntj1VMA==",
+      "requires": {
+        "prop-types": "^15.7.2"
+      }
+    },
     "@gar/promisify": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
@@ -26026,9 +26103,9 @@
       "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
     },
     "nanoid": {
-      "version": "3.1.30",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
-      "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ=="
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
+      "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
     },
     "natural-compare": {
       "version": "1.4.0",

+ 3 - 0
package.json

@@ -3,6 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@fortawesome/fontawesome-svg-core": "^1.2.36",
+    "@fortawesome/free-solid-svg-icons": "^5.15.4",
+    "@fortawesome/react-fontawesome": "^0.1.16",
     "@testing-library/jest-dom": "^5.16.1",
     "@testing-library/react": "^12.1.2",
     "@testing-library/user-event": "^13.5.0",

+ 0 - 38
src/App.css

@@ -1,38 +0,0 @@
-.App {
-  text-align: center;
-}
-
-.App-logo {
-  height: 40vmin;
-  pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
-  .App-logo {
-    animation: App-logo-spin infinite 20s linear;
-  }
-}
-
-.App-header {
-  background-color: #282c34;
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: calc(10px + 2vmin);
-  color: white;
-}
-
-.App-link {
-  color: #61dafb;
-}
-
-@keyframes App-logo-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}

+ 28 - 19
src/App.js

@@ -1,15 +1,15 @@
-import './App.css';
+import './App.scss';
 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  {PlayerbarConnect} from './components/Playerbar'
+import { PlayerbarConnect } from './components/Playerbar'
 
 import thunk from 'redux-thunk';
 import { createStore, combineReducers, applyMiddleware } from 'redux';
 import { Provider } from 'react-redux';
-import { Link, Route, Router, Switch } from 'react-router-dom';
+import { Route, Router, Switch } from 'react-router-dom';
 import createHistory from 'history/createBrowserHistory'
 
 export function jwtDecode(token) {
@@ -65,37 +65,46 @@ if (store.getState().auth?.token) {
   history.push('/login')
 }
 
+const GreetPg = () =>
+  <>
+    <h2>Welcome to online Player!</h2>
+    <div className='startpage__ulwrapper'>
+      <ul>
+        <li>
+          <strong className='highlightYellow'>Click "MY UPLOADS" - </strong><small>To see all of your uploaded tracks.</small>
+        </li>
+        <li>
+          <strong className='highlightGreen'>Click "NEW PLAYLIST" - </strong><small>To create new playlist.</small>
+        </li>
+        <li>
+          <strong>Drag 'n' drop track to playlist area - </strong><small>To upload the track and add it to current playlist.</small>
+        </li>
+        <li>
+          <strong>Drag a track within playlist - </strong><small>To chage the order of playlist tracks.</small>
+        </li>
+      </ul>
+    </div>
+  </>
+
 const Player = () =>
   <div>
-    {/* <header><Link to="/player">Player</Link></header> */}
-    <div style={{ border:'1px solid blue', display: 'flex', maxHeight: '89vh', minHeight:'30vh', overflow:'none'}}>
-      <aside style={{width: '30%', overflow:'auto'}}>
+    <div className='page'>
+      <aside className='sidebar' >
         <Sidebar.LogoutBtnConnect />
         <Sidebar.ProfileWindowDropzoneConnect />
         <Sidebar.UserTracksBtnConnect />
         <Sidebar.PlaylistAddConnect />
         <Sidebar.PlaylistsConnect />
       </aside>
-      <main style={{ border: '1px solid red', width: '80%', height:'inherit', overflow:'auto'}}>
+      <main className='page__window'>
         <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>Click "NEW PLAYLIST" - </strong><small>To create new playlist.</small></li>
-                <li><strong>Drag 'n' drop track to playlist area - </strong><small>To upload the track and add it to current playlist.</small></li>
-                <li><strong>Drag a track within playlist - </strong><small>To chage the order of playlist tracks.</small></li>
-                <li><strong>Click "MY TRACKS" - </strong><small>To see all of your uploaded tracks.</small></li>
-              </ul>
-            </div>
-          </>
+          <GreetPg />
         </Switch>
       </main>
     </div>
     <PlayerbarConnect />
-    {/* <PlayerBar /> */}
   </div>
 
 function App() {

+ 57 - 0
src/App.scss

@@ -0,0 +1,57 @@
+@import "./styles/Sidebar.scss";
+@import "./styles/Page.scss";
+@import "./styles/Playerbar.scss";
+@import "./styles/Forms.scss";
+
+body {
+  background-color: #100d23;
+  ::-webkit-scrollbar {
+    width: 12px;
+
+    &-track {
+      background-color: transparent;
+      border: 1px solid rgba(105, 105, 105, 0.384);
+    }
+
+    &-thumb {
+      background-color: #534f7459;
+
+      &:hover {
+        background-color: #534f74c7;
+      }
+    }
+  }
+
+  :hover {
+    transition: 0.1s;
+  }
+}
+
+.App {
+  text-align: center;
+  color: #70b2e7;
+}
+
+.highlightYellow {
+  color: yellow;
+  border-color: yellow;
+}
+
+.highlightRed {
+  color: red;
+  border-color: red;
+}
+
+.highlightGreen {
+  color: #05f09b;
+  border-color: #05f09b;
+}
+
+.highlightPaleGreen {
+  background-color: #0e6147;
+  color: #05f09b;
+}
+
+.lightText {
+  font-weight: 300;
+}

+ 8 - 13
src/actions/index.js

@@ -12,14 +12,12 @@ const actionTrackSet = (track, audio, playlist) =>
     volume: audio.volume
 })
 
+const actionTrackEject = () => ({type: 'EJECT_TRACK'})
 const actionTrackSetDuration = (time) => ({ type: 'SET_DURATION', duration: time })
-
 const actionTrackSetCurrTime = (time) => ({ type: 'SET_CURRTIME', currentTime: time })
-
 const actionTrackSetVolume = (value) => ({ type: 'SET_VOLUME', volume: value })
 const actionTrackPlay = () => ({ type: 'PLAY_TRACK' })
 const actionTrackPause = (currentTime) => ({ type: 'PAUSE_TRACK', currentTime: currentTime })
-
 const actionPlaylistSet = (playlist) => ({type: 'SET_PLAYLIST', playlist: playlist})
 const actionSetPlaylistIndex = (index) => ({type: 'SET_INDEX', playlistIndex: index})
 
@@ -36,7 +34,6 @@ export const switchTrack = (isForward, currentTrackIndex, playlist) =>
     dispatch => {
         let playlistLength = playlist.constructor.name ==='Array'? playlist.length - 1 : playlist.tracks.length - 1
         let tracks = playlist.constructor.name ==='Array'? playlist : playlist.tracks
-        console.log('stuff', tracks, playlistLength)
 
         if (isForward ? currentTrackIndex < playlistLength : currentTrackIndex > 0) {
             dispatch(setTrack(tracks[currentTrackIndex + (isForward ? 1 : -1)], playlist))
@@ -76,15 +73,6 @@ audio.onended = () => store.dispatch(switchTrack(true, store.getState().player.p
 audio.ondurationchange = (e) =>  store.dispatch(actionTrackSetDuration(e.target.duration))
 audio.ontimeupdate = (e) => store.dispatch(actionTrackSetCurrTime(e.target.currentTime))
 
-
-
-
-
-
-
-
-
-
 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 })
@@ -103,6 +91,13 @@ export const actionPromise = (name, promise) =>
 
 export const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
 export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
+export const actionFullLogout = () =>
+    dispatch => {
+        audio.pause()
+        dispatch(actionTrackEject())
+        dispatch(actionAuthLogout())
+    }
+
 export const actionLogin = (login, password) =>
     actionPromise('login', gql(`
   query log($login:String!, $password:String!) {

BIN
src/assets/default.png


+ 31 - 19
src/components/Login/index.js

@@ -5,31 +5,44 @@ import { connect } from 'react-redux';
 import { Link } from 'react-router-dom';
 import { history } from '../../App';
 
-const RegisterForm = ({ onRegister }) => {
+const RegisterForm = ({ onRegister, regData }) => {
   let [login, setLogin] = useState()
   let [password, setPassword] = useState()
   let [password2, setPassword2] = useState()
+  let [data, setData] = useState()
+  let [isReg, setIsReg] = useState(false)
+
+  useEffect(() => {
+    setData(regData)
+  }, [regData, data])
+
   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 />
+      <div className='forms'>
+        <h2 className='highlightYellow'>Registration</h2>
+        <input className='forms__input' type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)} />
+        <input className='forms__input' type="password" placeholder='Password' onChange={(e) => setPassword(e.target.value)} />
+        <input 
+          className='forms__input' 
+          disabled={!password} type="password" placeholder='Repeat Password *' 
+          onChange={(e) => setPassword2(e.target.value)} 
+        />
         <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>
+        {data?.payload && isReg? <small className='highlightGreen'>User {data.payload.login} successfully registered!</small> : 
+                        <button 
+                          className='forms__button highlightYellow' 
+                          disabled={!password || !login || password2 !== password} 
+                          onClick={() => {onRegister(login, password); setIsReg(true)}}
+                        >Register</button>}
         <br />
-        <Link to="/login">Back to log-in page</Link>
+        <Link className='sidebar__link highlightYellow' to="/login">Back to log-in page</Link>
       </div>
     </>
   )
 }
-export const RegisterFormConnect = connect(null, { onRegister: action.actionRegister })(RegisterForm)
+export const RegisterFormConnect = connect(state => ({regData: state.promise.registration || {}}), { onRegister: action.actionRegister })(RegisterForm)
 
 const LoginForm = ({ loged, onLogin }) => {
   let [login, setLogin] = useState()
@@ -44,22 +57,21 @@ const LoginForm = ({ loged, onLogin }) => {
   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 />
+      <div className='forms'>
+        <h2 className='highlightYellow'>Log-in</h2>
+        <input className='forms__input' type="text" placeholder='Login' onChange={(e) => setLogin(e.target.value)} />
+        <input className='forms__input' type="password" placeholder='Password' onChange={(e) => setPassword(e.target.value)} />
         <small style={{ color: 'red' }}>
           {loged.status === 'REJECTED' || (loged.status === 'RESOLVED' && !loged.payload)? 'invalid login or password' : ''}
         </small>
         <br />
         <button
+          className='forms__button highlightGreen'
           disabled={!password || !login}
           onClick={() => { onLogin(login, password) }}
         >Login</button>
         <p>- OR -</p>
-        <Link to="/registration">Register new user</Link>
+        <Link className='forms__link highlightYellow' to="/registration">Register new user</Link>
       </div>
     </>
   )

+ 35 - 31
src/components/Page/index.js

@@ -5,6 +5,8 @@ import { connect } from 'react-redux';
 import { useDropzone } from 'react-dropzone'
 import { sortableContainer, sortableElement } from 'react-sortable-hoc';
 import { arrayMoveImmutable } from 'array-move';
+import { faPlay, faPause} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 
 const Track = ({ track, playlist, player, setTrack, playTrack, pauseTrack }) => {
@@ -13,26 +15,25 @@ const Track = ({ track, playlist, player, setTrack, playTrack, pauseTrack }) =>
   useEffect(() => setPlayer(player), [player])
 
   return (
-    <li style={{ border: '1px solid black', display: 'flex', alignItems: 'center' }}>
-      <div style={{ marginRight: '2%', padding: '2%' }}>
-
-        {isPlay && _player?.isPlaying && _player.track?._id === track._id ?
-          <button style={{ fontSize: '3vh' }} onClick={() => { pauseTrack(); setPlay(false) }}>{`\u23F8`}</button> :
-          <button
-            style={{ fontSize: '3vh' }}
-            onClick={() => {
-              if (track?._id !== _player?.track?._id) setTrack(track, playlist)
-              playTrack()
-              setPlay(true)
-            }}
-          >{`\u23F5`}</button>
-        }
-
-      </div>
-      <div style={{ textAlign: 'left' }}>
-        <h5>{track.id3.artist || 'Artist: unknown'}</h5>
-        <h6>{track.id3.album || 'Album: unknown'}</h6>
-        <h5>{track.id3.title || track.originalFileName}</h5>
+    <li className={isPlay && _player?.isPlaying && _player.track?._id === track._id ? 'playlist__track highlightPaleGreen' : 'playlist__track'}>
+      {isPlay && _player?.isPlaying && _player.track?._id === track._id ?
+        <button 
+          className='highlightYellow' 
+          onClick={() => { pauseTrack(); setPlay(false) }}
+        ><FontAwesomeIcon style={{pointerEvents:'none'}} icon={faPause} /></button> :
+        <button
+          className='highlightGreen'
+          onClick={() => {
+            if (track?._id !== _player?.track?._id) setTrack(track, playlist)
+            playTrack()
+            setPlay(true)
+          }}
+        ><FontAwesomeIcon style={{pointerEvents:'none'}} icon={faPlay} /></button>
+      }
+      <div>
+        <small className='artist'>{track.id3.artist || 'Artist: unknown'}</small>
+        <small className='album'>{track.id3.album || 'Album: unknown'}</small>
+        <small className='title'>{track.id3.title || track.originalFileName}</small>
       </div>
     </li>
   )
@@ -49,7 +50,7 @@ const TrackConnect = connect(
 )(Track)
 
 const SortableItem = sortableElement(TrackConnect);
-const SortableContainer = sortableContainer(({ children }) => { return <ul>{children}</ul> });
+const SortableContainer = sortableContainer(({ children }) => <ul>{children}</ul> );
 
 const Playlist = ({ player, playlist, setPlaylist, updPlaylist, setIndex }) => {
   let [_tracks, setTracks] = useState()
@@ -70,9 +71,9 @@ const Playlist = ({ player, playlist, setPlaylist, updPlaylist, setIndex }) => {
 
   return (
     <>
-      <h2>{playlist[0]?.name || 'Playlist'}</h2>
+      <h2 className='highlightGreen'>{playlist[0]?.name || 'Playlist'}</h2>
       <SortableContainer onSortEnd={onSortEnd}>
-        {(_tracks || []).map((track, index) => <SortableItem index={index} track={track} playlist={playlist[0]} />)}
+        { (_tracks || []).map((track, index) => <SortableItem key={track._id} index={index} track={track} playlist={playlist[0]} />) }
       </SortableContainer>
     </>
   )
@@ -97,15 +98,16 @@ const PlaylistTrackDropzone = ({ playlist, uploadTrack }) => {
   }, [playlist])
 
   const onDrop = useCallback(acceptedFiles => {
-    uploadTrack(acceptedFiles[0], playlistId)
+    if(acceptedFiles[0].type === 'audio/mpeg') uploadTrack(acceptedFiles[0], playlistId)
   }, [uploadTrack, playlistId])
   const { getRootProps, isDragActive } = useDropzone({ onDrop })
   return (
     <div
       {...getRootProps()}
-      style={{ height: 'fit-content', border: `${isDragActive ? '1px solid mediumseagreen' : '1px solid black'}` }}
+      className='playlist'
+      style={{ border: `${isDragActive ? '1px solid yellow' : '1px solid transparent'}` }}
     >
-      {isDragActive ? <small>...drag here</small> : <small>to upload drag track here</small>}
+      <small className='lightText'>{isDragActive ? '...drop here' : 'to upload: drag and drop track here'}</small>
       <PlaylistConnect />
     </div>
   )
@@ -125,8 +127,8 @@ const UserTracks = ({ user, tracks }) => {
   let tracksRev = [...tracks].reverse()
   return (
     <>
-      <h2>{user.login || 'My'} uploaded tracks:</h2>
-      <ul>{(tracksRev || []).map(track => <TrackConnect track={track} playlist={tracksRev} />)}</ul>
+      <h2 className='highlightYellow'>{user.login || 'My'} uploaded tracks:</h2>
+      <ul>{(tracksRev || []).map(track => <TrackConnect key={track._id} track={track} playlist={tracksRev} />)}</ul>
     </>
   )
 }
@@ -138,15 +140,17 @@ const UserTracksConnect = connect(state => ({
 
 const UserTracksDropzone = ({ onLoad }) => {
   const onDrop = useCallback(acceptedFiles => {
-    onLoad(acceptedFiles[0])
+    if(acceptedFiles[0].type === 'audio/mpeg') onLoad(acceptedFiles[0])
   }, [onLoad])
+  
   const { getRootProps, isDragActive } = useDropzone({ onDrop })
   return (
     <div
       {...getRootProps()}
-      style={{ height: 'fit-content', border: `${isDragActive ? '1px solid mediumseagreen' : '1px solid black'}` }}
+      className='playlist'
+      style={{ height: 'fit-content', border: `${isDragActive ? '1px solid yellow' : '1px solid black'}` }}
     >
-      {isDragActive ? <small>...drag here</small> : <small>to upload drag track here</small>}
+      <small className='lightText'>{isDragActive ? '...drop here' : 'to upload: drag and drop track here'}</small>
       <UserTracksConnect />
     </div>
   )

+ 47 - 36
src/components/Playerbar.js

@@ -1,12 +1,14 @@
 import { useEffect, useState } from "react"
 import { connect } from "react-redux"
+import { faPlay, faPause, faForward, faBackward, faFastForward, faFastBackward, faVolumeUp } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import * as action from "../actions"
 
 
-const Timestamp = ({time}) =>
-    <small style={{margin: '0 10px'}}>
-        {Math.floor((time / 60) % 60) < 10? `0` + Math.floor((time / 60) % 60) : Math.floor((time / 60) % 60)} : 
-        {Math.floor(time % 60) < 10?  `0`+ Math.floor(time % 60) : Math.floor(time % 60)}
+const Timestamp = ({ time }) =>
+    <small className="timestamp highlightYellow">
+        {Math.floor((time / 60) % 60) < 10 ? `0` + Math.floor((time / 60) % 60) : Math.floor((time / 60) % 60)}:
+        {Math.floor(time % 60) < 10 ? `0` + Math.floor(time % 60) : Math.floor(time % 60)}
     </small>
 
 const Playerbar = ({ player, playTrack, pauseTrack, switchTrack, setTrackVolume, setCurrentTime }) => {
@@ -17,47 +19,56 @@ const Playerbar = ({ player, playTrack, pauseTrack, switchTrack, setTrackVolume,
     }, [player, _player])
 
     return (
-        <>
-            {_player?.track ?  
-                <footer style={{display: `${_player?.track ? 'block' : 'none'}`, width: '95%', margin:'0 auto'}}>
-                    <input
-                        style={{width:'100%'}}
-                        type="range" min="0" max={_player?.duration} step="any"
-                        value={_player?.currentTime}
-                        onChange={(e) => setCurrentTime(e.target.value)}
-                    />
-                    <div style={{display:"flex", justifyContent:"space-between", width:'95%', margin:'0 auto'}}>
-                        <Timestamp time={_player?.currentTime} />
-                        {_player ? (_player.track?.id3?.artist && _player.track?.id3?.title ?
-                            <strong>{_player.track?.id3?.artist} - {_player.track?.id3?.title}</strong> :
-                            <strong>{_player.track?.originalFileName}</strong>) : ''}
+        <footer className="playerbar">
+            {_player?.track ?
+                <div style={{ display: `${_player?.track ? 'block' : 'none'}` }}>
+                    <div>
+                        <input
+                            className="playerbar__range plumline yellowThumb"
+                            style={{ width: '95%' }}
+                            type="range" min="0" max={_player?.duration || 0} step="any"
+                            value={_player?.currentTime}
+                            onChange={(e) => setCurrentTime(e.target.value)}
+                        />
+                    </div>
+                    <div className="playerbar__info">
+                        <Timestamp className="playerbar" time={_player?.currentTime} />
+                        <strong className="highlightYellow">
+                            <small className="lightText highlightGreen">{(_player.playlist.name || 'Uploads') + ' : '}</small>
+                            {(_player.track?.id3?.artist && _player.track?.id3?.title ?
+                                _player.track?.id3?.artist + ' - ' + _player.track?.id3?.title :
+                                _player.track?.originalFileName)}
+                        </strong>
                         <Timestamp time={_player?.duration} />
                     </div>
-                    <div style={{marginTop:'0.5%'}}>
-                        <button
-                            style={{ fontSize: '2vh' }}
-                            onClick={() => switchTrack(false, _player?.playlistIndex, _player?.playlist)}
-                        >
-                            {_player?.playlistIndex === 0 ? `\u23F4` : `\u23EE`}
+                    <div style={{ marginTop: '0.5%' }}>
+                        <button onClick={() => switchTrack(false, _player?.playlistIndex, _player?.playlist)}>
+                            {_player?.playlistIndex === 0 ? <FontAwesomeIcon icon={faFastBackward} /> :
+                                <FontAwesomeIcon icon={faBackward} />}
                         </button>
 
                         {_player?.isPlaying ?
-                            <button style={{ fontSize: '2vh' }} onClick={() => pauseTrack()}>{`\u23F8`}</button> :
-                            <button style={{ fontSize: '2vh' }} onClick={() => playTrack()}>{`\u23F5`}</button>
+                            <button onClick={() => pauseTrack()}><FontAwesomeIcon icon={faPause} /></button> :
+                            <button onClick={() => playTrack()}><FontAwesomeIcon icon={faPlay} /></button>
                         }
 
-                        <button
-                            style={{ fontSize: '2vh' }}
-                            onClick={() => switchTrack(true, _player?.playlistIndex, _player?.playlist)}
-                        >
-                            {_player?.playlistIndex === (_player?.playlist?.constructor.name === 'Array'? _player?.playlist?.length - 1 : _player?.playlist?.tracks.length - 1) ? 
-                                `\u23F5` : `\u23ED`}
+                        <button onClick={() => switchTrack(true, _player?.playlistIndex, _player?.playlist)}>
+                            {_player?.playlistIndex === (_player?.playlist?.constructor.name === 'Array' ?
+                                _player?.playlist?.length - 1 : _player?.playlist?.tracks.length - 1) ?
+                                <FontAwesomeIcon icon={faFastForward} /> :
+                                <FontAwesomeIcon icon={faForward} />}
                         </button>
-                        <input style={{marginLeft: '1%'}} type="range" min="0" max="1" step="any" onChange={(e) => setTrackVolume(e.target.value)} />
+                        <label style={{ marginLeft: '1%' }}>
+                            <FontAwesomeIcon icon={faVolumeUp} />
+                            <input
+                                className="playerbar__range blueline blueThumb"
+                                type="range" min="0" max="1" step="any"
+                                onChange={(e) => setTrackVolume(e.target.value)} 
+                            />
+                        </label>
                     </div>
-                    <small>{_player?.playlistIndex}</small>
-                </footer> : <div style={{width:'inherit', height:'inherit', padding:'2em'}}>C'mon, Push the play button on some track :)</div>}
-        </>
+                </div> : <></>}
+        </footer>
     )
 }
 

+ 41 - 59
src/components/Sidebar/index.js

@@ -1,36 +1,34 @@
 import * as action from '../../actions'
+import plugPic from '../../assets/default.png'
 
 import { useState, useEffect, useCallback } from 'react';
 import { connect } from 'react-redux';
 import { Link } from 'react-router-dom';
-import { history, backendURL } from '../../App';
+import { backendURL } from '../../App';
 import { useDropzone } from 'react-dropzone'
+import { faPlus, faBan, faSignOutAlt, faUpload} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 
 const LogoutBtn = ({ onLogout }) =>
-  <div
-    style={{ border: '1px solid black', backgroundColor: 'red', color: 'white' }}
-    onClick={() => { onLogout(); history.push('/login') }}
-  >log-out[X]</div>
+  <Link
+    to='/login'
+    className='sidebar__link highlightRed'
+    onClick={() =>  onLogout()}
+  >LOG-OUT <FontAwesomeIcon icon={faSignOutAlt}/></Link>
 
-export const LogoutBtnConnect = connect(null, { onLogout: action.actionAuthLogout })(LogoutBtn)
+//export const LogoutBtnConnect = connect(null, { onLogout: action.actionAuthLogout })(LogoutBtn)
+export const LogoutBtnConnect = connect(null, { onLogout: action.actionFullLogout })(LogoutBtn)
 
 const ProfileWindow = ({ user }) => {
   let [userInfo, setUserInfo] = useState(user.payload)
-
-  useEffect(() => {
-    setUserInfo(user.payload)
-  }, [user, userInfo])
+  useEffect(() => setUserInfo(user.payload), [user, userInfo])
 
   return (
-    <section>
+    <section >
       <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'
-      />
+      {userInfo?.avatar?.url ? 
+                        <img src={backendURL + '/' + userInfo?.avatar?.url} alt='avatar'/> : 
+                        <img src={plugPic} alt='avatar' />}
     </section>
   )
 }
@@ -38,15 +36,18 @@ export const ProfileWindowConnect = connect(state => ({ user: state.promise.user
 
 const ProfileWindowDropzone = ({ onLoad }) => {
   const onDrop = useCallback(acceptedFiles => {
-    onLoad(acceptedFiles[0])
+    if(acceptedFiles[0].type === 'image/png' || acceptedFiles[0].type === 'image/jpeg') onLoad(acceptedFiles[0])
   }, [onLoad])
   const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
 
   return (
-    <div {...getRootProps()} style={isDragActive ? { border: '1px solid mediumseagreen' } : { border: '1px solid black' }}>
+    <div {...getRootProps()} 
+      className='sidebar__profile' 
+      style={isDragActive ? { backgroundColor: '#0e6147' } : { backgroundColor: 'transparent' }}
+    >
       <input {...getInputProps()} />
       <ProfileWindowConnect />
-      {isDragActive ? <small>drag here...</small> : <small>change avatar</small>}
+      <small className='lightText'>{isDragActive ? '...drop here' : 'change avatar'}</small>
     </div>
   )
 }
@@ -54,23 +55,9 @@ export const ProfileWindowDropzoneConnect = connect(null, { onLoad: action.actio
 
 const UserTracksBtn = ({ userId }) => {
   let [_id, setId] = useState()
-  useEffect(() => {
-    console.log('CHENG', userId)
-    setId(userId)
-  }, [userId])
-
-  return (
-    <Link
-      to={`/player/tracks/:${_id}`}
-      style={{
-        display: 'block',
-        backgroundColor: 'purple',
-        color: 'white',
-        margin: '5px',
-        padding: '5px'
-      }}
-    >My uploads</Link>
-  )
+  useEffect(() => setId(userId), [userId])
+  
+  return (<Link to={`/player/tracks/:${_id}`} className='sidebar__link highlightYellow'>UPLOADS <FontAwesomeIcon icon={faUpload}/></Link>)
 }
 export const UserTracksBtnConnect = connect(state => ({ userId: state.promise.userData?.payload?._id || '' }))(UserTracksBtn)
 
@@ -82,28 +69,28 @@ const PlaylistAdd = ({ addPlaylist }) => {
       {
         !clicked ?
           <button
-            style={{ border: '1px solid black', backgroundColor: 'mediumseagreen', width: '95%', padding: '5px', margin: '5px' }}
+            className='sidebar__button highlightPaleGreen'
             onClick={() => setClicked(true)}
           >NEW PLAYLIST</button>
           :
-          <div style={{ width: '95%', margin: '0 auto' }}>
+          <div className='sidebar__addpanel'>
             <input
-              style={{ width: '72%', padding: '5px' }}
-              placeholder='Playlist name'
+              placeholder='NEW PLATLIST NAME'
               value={name}
               onChange={(e) => setName(e.target.value)}
             />
+            <div className='sidebar__addpanel btnWrapper'>
+              <button
+                disabled={!name}
+                className='highlightGreen'
+                onClick={() => { addPlaylist(name); setClicked(false); setName(''); }}
+              ><FontAwesomeIcon icon={faPlus} /></button>
 
-            <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>
+              <button
+                className='highlightRed'
+                onClick={() => { setClicked(false); setName('') }}
+              ><FontAwesomeIcon icon={faBan} /></button>
+            </div>
           </div>
       }
     </div>
@@ -111,17 +98,12 @@ const PlaylistAdd = ({ addPlaylist }) => {
 }
 export const PlaylistAddConnect = connect(null, { addPlaylist: action.actionAddPlaylist })(PlaylistAdd)
 
-const Playlists = ({ playlists }) => {
+const Playlists = ( { playlists }) => {
   return (
     <div>
       {
         playlists?.payload ? playlists.payload.map(item => {
-          return (
-            <Link
-              style={{ display: 'block', backgroundColor:'darkcyan', color: 'cyan', margin: '5px', padding: '5px' }}
-              to={`/player/playlist/:${item._id}`}
-            >{item.name}</Link>
-          )
+          return (<Link key={item._id} className='sidebar__link' to={`/player/playlist/:${item._id}`}>{item.name}</Link> )
         }).reverse() : ''
       }
     </div>

+ 4 - 3
src/index.js

@@ -5,9 +5,10 @@ import App from './App';
 import reportWebVitals from './reportWebVitals';
 
 ReactDOM.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>,
+  <App />,
+  // <React.StrictMode>
+  //   <App />
+  // </React.StrictMode>,
   document.getElementById('root')
 );
 

+ 1 - 4
src/reducers/index.js

@@ -41,10 +41,7 @@ export function playerReducer(state, { type, track, playlist, duration, currentT
     if (type === 'SET_DURATION') return { ...state, 'duration': duration }
     if (type === 'SET_VOLUME') return { ...state, 'volume': volume }
     if (type === 'SET_PLAYLIST')  return { ...state, 'playlist': playlist} 
-    if (type === 'SET_INDEX')  {
-        console.log('SEt_INDEX', playlistIndex)
-        return { ...state, 'playlistIndex': playlistIndex}
-    } 
+    if (type === 'SET_INDEX')   return { ...state, 'playlistIndex': playlistIndex} 
 
     if (type === 'SET_TRACK') {
         return {

+ 64 - 0
src/styles/Forms.scss

@@ -0,0 +1,64 @@
+.forms {
+    background-color: #131341;
+    width: fit-content;
+    margin: 0 auto;
+    padding: 2%;
+    font-size: large;
+    border-radius: 5%;
+    box-shadow: 0 0 15px black;
+
+    &__link {
+        font-size: medium;
+        display: block;
+        text-decoration: none;
+        border-left: 5px solid #05f09b;
+        background-color: #100d23;
+        color: #05f09b;
+        margin: 5px;
+        padding: 5px;
+    
+        &:hover {
+          background-color: #323151;
+        }
+    }
+
+    &__input {
+        display: block;
+        font-size: x-large;
+        border-style: none;
+        outline: none;
+        background: transparent;
+        margin: 5% 0 0 0;
+        border-bottom: 2px solid gray;
+        color: #83d6f6;
+
+        &:focus {
+            border-bottom: 2px solid #05f09b;
+            color: #05f09b;
+        }
+
+        &:disabled {
+            &::placeholder { color: rgba(128, 128, 128, 0.274); }
+            border-color: rgba(128, 128, 128, 0.274);
+        }
+    }
+
+    &__button {
+        display: block;
+        margin: 0 auto;
+        margin-top: 10px;
+        font-size: large;
+        padding: 3%;
+        background: transparent;
+        border-style: none;
+        border: 1px solid;
+        &:active {
+            background-color: #0e6147;
+        }
+
+        &:disabled {
+            color: grey;
+            border-color: #0e6147;
+        }
+    }
+}

+ 75 - 0
src/styles/Page.scss

@@ -0,0 +1,75 @@
+@import './adaptivefont.scss';
+
+.page {
+    display: flex;
+    height: 89vh;
+    overflow: none;
+
+    &__window {
+        width: 80%;
+        height: inherit;
+        overflow: auto;
+    }
+}
+
+.startpage {
+    &__ulwrapper {
+        width: 50%;
+        margin: 0 auto;
+
+        ul {
+            text-align: start;
+            small {
+                color: whitesmoke;
+            }
+        }
+    }
+}
+
+.playlist {
+    background-color: #1e1d45;
+    height: fit-content;
+    width: 90%;
+    margin: 0 auto;
+    padding: 1% 2%;
+    &__track {
+        display: flex;
+        align-items: center;
+        background-color: #2a3da941;
+        margin: 10px;
+        padding: 2% 0;
+        color: #83d6f6;
+        box-shadow: 0 0 5px black;
+
+        button {
+            display: block;
+            background-color: #1e1d45;
+            border-style: solid;
+            font-size: x-large;
+            padding: 1%;
+            margin: 0 5%;
+        }
+
+        small,
+        strong {
+            display: block;
+        }
+
+        .artist {
+            @include adaptive-font(16, 12);
+            font-weight: 700;
+            text-align: start;
+        }
+        .album {
+            @include adaptive-font(14, 10);
+            font-weight: 400;
+            text-align: start;
+        }
+        .title {
+            @include adaptive-font(18, 13);
+            //font-size: large;
+            font-weight: 700;
+            text-align: start;
+        }
+    }
+}

+ 76 - 0
src/styles/Playerbar.scss

@@ -0,0 +1,76 @@
+.playerbar {
+    position: sticky;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    padding-bottom: 1%;
+    background-color: #131341;
+
+    &__range {
+        margin: 0 10px 6px 10px;
+        -webkit-appearance: none;
+        outline: none;
+        cursor: pointer;
+        
+        &::-webkit-slider-thumb {
+            -webkit-appearance: none;
+            border: 1px solid #000000;
+            height: 20px;
+            width: 20px;
+            border-radius: 100%;
+            cursor: pointer;
+            margin-top: -8px;
+            box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
+        }
+
+        &::-webkit-slider-runnable-track {
+            width: 100%;
+            border: 1px solid rgba(255, 255, 0, 0.26);
+            height: 4px;
+        }
+    }
+
+    &__info {
+        display: flex;
+        justify-content: space-between;
+        width: 95%;
+        margin: 0 auto;
+    }
+
+    button {
+        font-size: large;
+        padding: 0.5%;
+        background-color: yellow;
+        border: none;
+        margin: 0 5px;
+    }
+}
+
+.timestamp {
+    margin: 0 10px;
+    font-weight: 500;
+}
+
+.plumline {
+    &::-webkit-slider-runnable-track {
+        background: #2f1dce;
+    }
+}
+
+.blueline {
+    &::-webkit-slider-runnable-track {
+        background: #70b2e7;
+    }
+}
+
+.yellowThumb {
+    &::-webkit-slider-thumb {
+        background: yellow;
+    }
+}
+
+.blueThumb {
+    &::-webkit-slider-thumb {
+        background: #499fe6;
+    }
+}

+ 82 - 0
src/styles/Sidebar.scss

@@ -0,0 +1,82 @@
+@import './adaptivefont.scss';
+
+.sidebar {
+    background-color: #1e1d45;
+    width: 30%; 
+    overflow:auto;
+  
+    &__link {
+      @include adaptive-font(20, 13);
+      //font-size: medium;
+      display: block;
+      text-decoration: none;
+      border-left: 5px solid #05f09b;
+      background-color: #100d23;
+      color: #05f09b;
+      margin: 5px;
+      padding: 5px;
+  
+      &:hover {
+        background-color: #323151;
+      }
+    }
+  
+    &__button {
+      font-size: medium;
+      width: 100%;
+      border-style: none;
+      padding: 10px;
+  
+      &:active {
+        background-color: #05f09b;
+        color: #0e6147;
+      }
+    }
+  
+    &__addpanel {
+      display: flex;
+      width: 95%;
+      margin: 0 auto;
+      border: 1px solid #05f09b;
+      padding: 0.5%;
+      
+      input {
+        @include adaptive-font(20, 12);
+        //font-size: medium;
+        outline: none;
+        background-color: transparent;
+        width: 80%;
+        color: #05f09b;
+        border-style: none;
+      }
+  
+      .btnWrapper {
+        display: inline-block;
+        width: 20%;
+        border: none;
+  
+        button {
+          border: none;
+          background-color: transparent;
+          font-size: large;
+          padding: 4% 5% 2% 5%;
+          margin: 0 2%;
+        }
+      }
+    }
+  
+    &__profile {
+      margin: 0 auto;
+      h3 {
+        font-size: large;
+      }
+      img {
+        max-width: 45%;
+        //max-width: 100px;
+        height: auto;
+        border-radius: 10%;
+        border: 2px solid #70b2e7;
+        box-shadow: 0 0 15px black;
+      }
+    }
+  }

+ 13 - 0
src/styles/adaptivefont.scss

@@ -0,0 +1,13 @@
+$maxWidth: 1280;
+@mixin adaptive-font($pcSize, $mobSize) {
+  $additionSize: $pcSize - $mobSize;
+  $additionMobSize: $additionSize + $additionSize * 0.7;
+  //  for all screen width less than 767px(iPad)
+  @media (max-width: 767px) {
+    font-size: calc(#{$mobSize + px} + #{$additionMobSize} * ((100vw - 350px) / #{$maxWidth}));
+  }
+  // for all screen width more than 767px
+  @media (min-width: 767px) {
+    font-size: calc(#{$mobSize + px} + #{$additionSize} * (100vw / #{$maxWidth}));
+  }
+}