Browse Source

redux + audio state for player

Rostyslav Nahornyi 2 years ago
parent
commit
db326a7b74

+ 9 - 0
.eslintrc.json

@@ -0,0 +1,9 @@
+{
+    "env": {
+        "browser": true,
+        "es2021": true
+    },
+    "rules": {
+        "no-unused-vars": "off"
+    }
+}

+ 156 - 14
package-lock.json

@@ -17,10 +17,17 @@
         "@testing-library/user-event": "^13.5.0",
         "react": "^18.0.0",
         "react-dom": "^18.0.0",
+        "react-redux": "^8.0.1",
         "react-router-dom": "^6.3.0",
         "react-scripts": "5.0.1",
+        "redux": "^4.2.0",
+        "redux-thunk": "^2.4.1",
         "styled-components": "^5.3.5",
         "web-vitals": "^2.1.4"
+      },
+      "devDependencies": {
+        "eslint": "^8.14.0",
+        "eslint-plugin-react": "^7.29.4"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -2154,9 +2161,9 @@
       "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
-      "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
+      "integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
@@ -3908,6 +3915,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "node_modules/@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4096,6 +4112,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "node_modules/@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -6926,11 +6947,11 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.13.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz",
-      "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
+      "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
       "dependencies": {
-        "@eslint/eslintrc": "^1.2.1",
+        "@eslint/eslintrc": "^1.2.2",
         "@humanwhocodes/config-array": "^0.9.2",
         "ajv": "^6.10.0",
         "chalk": "^4.0.0",
@@ -13696,6 +13717,49 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-redux": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz",
+      "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8 || ^17.0 || ^18.0",
+        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react-native": ">=0.59",
+        "redux": "^4"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-redux/node_modules/react-is": {
+      "version": "18.0.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.0.0.tgz",
+      "integrity": "sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw=="
+    },
     "node_modules/react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -13873,6 +13937,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "node_modules/redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "peerDependencies": {
+        "redux": "^4"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -15561,6 +15641,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz",
+      "integrity": "sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0-rc"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -17987,9 +18075,9 @@
       "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
     },
     "@eslint/eslintrc": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
-      "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
+      "integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
       "requires": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
@@ -19170,6 +19258,15 @@
         "@types/node": "*"
       }
     },
+    "@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "requires": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -19358,6 +19455,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -21436,11 +21538,11 @@
       }
     },
     "eslint": {
-      "version": "8.13.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz",
-      "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==",
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
+      "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
       "requires": {
-        "@eslint/eslintrc": "^1.2.1",
+        "@eslint/eslintrc": "^1.2.2",
         "@humanwhocodes/config-array": "^0.9.2",
         "ajv": "^6.10.0",
         "chalk": "^4.0.0",
@@ -26177,6 +26279,26 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-redux": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.1.tgz",
+      "integrity": "sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w==",
+      "requires": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "18.0.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.0.0.tgz",
+          "integrity": "sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw=="
+        }
+      }
+    },
     "react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -26310,6 +26432,20 @@
         "strip-indent": "^3.0.0"
       }
     },
+    "redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "requires": {}
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -27555,6 +27691,12 @@
         "punycode": "^2.1.0"
       }
     },
+    "use-sync-external-store": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz",
+      "integrity": "sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw==",
+      "requires": {}
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

+ 7 - 0
package.json

@@ -12,8 +12,11 @@
     "@testing-library/user-event": "^13.5.0",
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
+    "react-redux": "^8.0.1",
     "react-router-dom": "^6.3.0",
     "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-thunk": "^2.4.1",
     "styled-components": "^5.3.5",
     "web-vitals": "^2.1.4"
   },
@@ -40,5 +43,9 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "eslint": "^8.14.0",
+    "eslint-plugin-react": "^7.29.4"
   }
 }

+ 1 - 0
src/App.js

@@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
 import { Home, Playlists, Profile, Queue, Tracks } from "./pages";
 import { history } from "./utils/history";
 
+// history.listen(() => console.log(history.location))   
 
 const App = () => {
     return (

+ 4 - 4
src/Components/LeftBar/LeftBar.jsx

@@ -11,7 +11,7 @@ import ProfileIcon from "../../assets/profile_icon.svg";
 import QueueIcon from "../../assets/queue_icon.svg";
 import TrackIcon from "../../assets/track_icon.svg";
 import PlaylistIcon from "../../assets/playlist_icon.svg";
-import { push } from "../../utils/history";
+import { back, push } from "../../utils/history";
 
 const tabs = [
     {
@@ -45,10 +45,10 @@ const LeftBar = () => {
     return (
         <Wrapper>
             <Navbar>
-                <BtnBack>
-                    <IconBack />
+                <BtnBack onClick={() => back()}>
+                    <IconBack/>
                 </BtnBack>
-                <Link to="/">
+                <Link to="/" onClick={() => push(ROUTES.HOME)}>
                     <Logo>
                         <Image src={logo} alt="logo" />
                         <Text>Audio Player</Text>

+ 1 - 1
src/Components/LeftBar/style.js

@@ -4,7 +4,7 @@ export const Wrapper = styled.div`
     width: 20%;
     display: flex;
     flex-direction: column;
-    background: #1c2125;
+    background: rgba(24,25,29,255);
     padding: 5px 0 0 10px;
 `;
 

+ 29 - 21
src/Components/Player/Player.jsx

@@ -1,4 +1,11 @@
 import React, { useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import {
+    togglePlay,
+    toggleRepeat,
+    setVolume,
+    setCurrentTime,
+} from "../../redux/actions/creators/audio";
 import {
     ButtonCollapse,
     ButtonVolume,
@@ -31,16 +38,10 @@ import VolumeUpIcon from "../../assets/volume_up_icon.svg";
 import VolumeStopIcon from "../../assets/volume_stop_icon.svg";
 
 const Player = () => {
-    const [isPlaying, setIsPlaying] = useState(false);
-    const [duration, setDuration] = useState(0);
-    const [currentTime, setCurrentTime] = useState(0);
-    const [track, setTrack] = useState({}); // props track
-    const [playlist, setPlaylist] = useState([]); // arr with tracks and their props
-    const [playlistIndex, setPlaylistIndex] = useState([]);
-    const [volume, setVolume] = useState(50);
+    const dispatch = useDispatch();
+    const state = useSelector((state) => state.audio);
 
     const [isCollapsed, setIsCollapsed] = useState(false);
-    const [isRepeated, setIsRepeated] = useState(false);
 
     return (
         <Wrapper isCollapsed={isCollapsed}>
@@ -49,10 +50,10 @@ const Player = () => {
                 <Audio
                     type={"range"}
                     min={0}
-                    max={100}
-                    value={currentTime}
+                    max={state.duration}
+                    value={state.currentTime}
                     onChange={(e) => {
-                        setCurrentTime(e.target.value);
+                        dispatch(setCurrentTime(+e.target.value));
                     }}
                 />
                 <ButtonCollapse
@@ -71,31 +72,38 @@ const Player = () => {
                         <ShuffleButton src={ShuffleIcon} />
                         <PreviousButton src={PreviousIcon} />
                         <StatusButton
-                            src={isPlaying ? StopIcon : PlayIcon}
-                            isPlaying={isPlaying}
-                            onClick={() => setIsPlaying(!isPlaying)} // redux
+                            src={state.isPlaying ? StopIcon : PlayIcon}
+                            isPlaying={state.isPlaying}
+                            onClick={() =>
+                                dispatch(
+                                    togglePlay(state.isPlaying ? false : true)
+                                )
+                            }
                         />
                         <NextButton src={NextIcon} />
                         <RepeatButton
                             src={RepeatIcon}
-                            isRepeated={isRepeated}
-                            onClick={() => setIsRepeated(!isRepeated)} // change to redux
+                            isRepeated={state.isRepeated}
+                            onClick={() =>
+                                dispatch(toggleRepeat(!state.isRepeated))
+                            }
                         />
                     </MainButtons>
                     <VolumeSettings>
                         <ButtonVolume
-                            src={volume ? VolumeUpIcon : VolumeStopIcon}
+                            src={state.volume ? VolumeUpIcon : VolumeStopIcon} // redux
                             onClick={() =>
-                                volume ? setVolume(0) : setVolume(100) // redux
+                                dispatch(setVolume(state.volume ? 0 : 1))
                             }
                         />
                         <Volume
                             type={"range"}
                             min={0}
-                            max={100}
-                            value={volume}
+                            max={1}
+                            step={0.01}
+                            value={state.volume}
                             onChange={(e) => {
-                                setVolume(e.target.value);
+                                dispatch(setVolume(+e.target.value));
                             }}
                         ></Volume>
                     </VolumeSettings>

+ 6 - 4
src/Components/Player/style.js

@@ -5,9 +5,11 @@ brightness(109%) contrast(109%);`;
 const darkFilter = `invert(48%) sepia(3%) saturate(4%) hue-rotate(326deg) brightness(110%) contrast(78%);`;
 
 export const Wrapper = styled.div`
-    height: ${({ isCollapsed }) => (isCollapsed ? "30px" : "75px")};
+    height: ${({ isCollapsed }) => (isCollapsed ? "30px" : "80px")};
     flex-shrink: 0;
-    background: #0f0e0e;
+
+    background: #1a1a1a;
+    border-top: 3px solid #525252;
     transition: 0.4s;
 
     display: flex;
@@ -44,7 +46,7 @@ export const Audio = styled.input`
         border-radius: 5px;
         background: rgb(100, 100, 100);
         cursor: pointer;
-        margin-top: -1.8px;
+        margin-top: -2.2px;
     }
 
     &::-webkit-slider-runnable-track {
@@ -109,7 +111,7 @@ export const StatusButton = styled.img`
     width: 40px;
     height: 40px;
     filter: invert(98%) sepia(0%) saturate(303%) hue-rotate(143deg) brightness(88%) contrast(84%);
-    transition: 0.6s;
+    transition: 1s;
 
     &:hover {
         cursor: pointer;

+ 14 - 7
src/Pages/Playlists/Playlists.jsx

@@ -1,10 +1,17 @@
-import React from 'react'
-import { LeftBar } from "../../components";
+import React from "react";
+import { LeftBar, Player } from "../../components";
+import { Content, Main, Wrapper } from "./styles";
 
 const Playlists = () => {
-  return (
-    <LeftBar></LeftBar>
-  )
-}
+    return (
+        <Wrapper>
+            <Main>
+                <LeftBar />
+                <Content></Content>
+            </Main>
+            <Player />
+        </Wrapper>
+    );
+};
 
-export default Playlists
+export default Playlists;

+ 16 - 0
src/Pages/Playlists/styles.js

@@ -0,0 +1,16 @@
+import styled from "styled-components";
+
+export const Wrapper = styled.div`
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+`;
+
+export const Main = styled.div`
+    display: flex;
+    flex-grow: 1;
+`;
+
+export const Content = styled.main`
+    width: 80%;
+`;

+ 14 - 7
src/Pages/Profile/Profile.jsx

@@ -1,10 +1,17 @@
-import React from 'react'
-import { LeftBar } from "../../components";
+import React from "react";
+import { LeftBar, Player } from "../../components";
+import { Content, Main, Wrapper } from "./styles";
 
 const Profile = () => {
-  return (
-    <LeftBar>Profile</LeftBar>
-  )
-}
+    return (
+        <Wrapper>
+            <Main>
+                <LeftBar />
+                <Content></Content>
+            </Main>
+            <Player />
+        </Wrapper>
+    );
+};
 
-export default Profile
+export default Profile;

+ 16 - 0
src/Pages/Profile/styles.js

@@ -0,0 +1,16 @@
+import styled from "styled-components";
+
+export const Wrapper = styled.div`
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+`;
+
+export const Main = styled.div`
+    display: flex;
+    flex-grow: 1;
+`;
+
+export const Content = styled.main`
+    width: 80%;
+`;

+ 14 - 7
src/Pages/Queue/Queue.jsx

@@ -1,10 +1,17 @@
-import React from 'react'
-import { LeftBar } from "../../components";
+import React from "react";
+import { LeftBar, Player } from "../../components";
+import { Content, Main, Wrapper } from "./styles";
 
 const Queue = () => {
-  return (
-    <LeftBar></LeftBar>
-  )
-}
+    return (
+        <Wrapper>
+            <Main>
+                <LeftBar />
+                <Content></Content>
+            </Main>
+            <Player />
+        </Wrapper>
+    );
+};
 
-export default Queue
+export default Queue;

+ 16 - 0
src/Pages/Queue/styles.js

@@ -0,0 +1,16 @@
+import styled from "styled-components";
+
+export const Wrapper = styled.div`
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+`;
+
+export const Main = styled.div`
+    display: flex;
+    flex-grow: 1;
+`;
+
+export const Content = styled.main`
+    width: 80%;
+`;

+ 14 - 7
src/Pages/Tracks/Tracks.jsx

@@ -1,10 +1,17 @@
-import React from 'react'
-import { LeftBar } from "../../components";
+import React from "react";
+import { LeftBar, Player } from "../../components";
+import { Content, Main, Wrapper } from "./styles";
 
 const Tracks = () => {
-  return (
-    <LeftBar></LeftBar>
-  )
-}
+    return (
+        <Wrapper>
+            <Main>
+                <LeftBar />
+                <Content></Content>
+            </Main>
+            <Player />
+        </Wrapper>
+    );
+};
 
-export default Tracks
+export default Tracks;

+ 16 - 0
src/Pages/Tracks/styles.js

@@ -0,0 +1,16 @@
+import styled from "styled-components";
+
+export const Wrapper = styled.div`
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+`;
+
+export const Main = styled.div`
+    display: flex;
+    flex-grow: 1;
+`;
+
+export const Content = styled.main`
+    width: 80%;
+`;

BIN
src/assets/little.mp3


BIN
src/assets/song.mp3


+ 4 - 0
src/hooks/useAuth.js

@@ -0,0 +1,4 @@
+    
+export const useAuth = () => {
+    return !!localStorage.getItem('authToken');
+}

+ 10 - 2
src/index.js

@@ -1,7 +1,15 @@
 import ReactDOM from "react-dom/client";
+import { Provider } from "react-redux";
+import store from './redux/store.js'
 import App from "./App";
-import './style.css';
+import "./style.css";
 
 const root = ReactDOM.createRoot(document.getElementById("root"));
 
-root.render(<App />);
+root.render(
+    <Provider store={store}>
+        <App />
+    </Provider>
+);
+
+// store.subscribe(() => console.log(store.getState()))

+ 57 - 0
src/redux/actions/creators/audio.js

@@ -0,0 +1,57 @@
+import song from "../../../assets/song.mp3"; // delete
+import song2 from "../../../assets/little.mp3"; // delete
+
+import store from "../../store";
+import types from "../types";
+
+const audio = new Audio(song);
+
+export const togglePlay = (status) => {
+    status ? audio.play() : audio.pause();
+    return { type: types.TOGGLE_PLAY, payload: status };
+};
+
+export const setDuration = (e) => {
+    const value = e.target.duration;
+
+    store.dispatch({
+        type: types.SET_DURATION,
+        payload: value,
+    });
+};
+
+export const setCurrentTime = (value) => {
+    audio.currentTime = value;
+
+    return { type: types.SET_CURRENT_TIME, payload: value };
+};
+
+export const setVolume = (value) => {
+    audio.volume = value;
+
+    return { type: types.SET_VOLUME, payload: value };
+};
+
+export const toggleRepeat = (status) => {
+    audio.loop = status;
+
+    return { type: types.TOGGLE_REPEAT, payload: status };
+};
+
+// audio listeners
+
+const onEnded = () => {
+    if (!audio.loop) {
+        //next track if exists(if no toggle_play)
+    }
+};
+const onTimeUpdate = (e) => {
+    store.dispatch({
+        type: types.SET_CURRENT_TIME,
+        payload: e.target.currentTime,
+    });
+};
+
+audio.addEventListener("ended", onEnded);
+audio.addEventListener("timeupdate", onTimeUpdate);
+audio.addEventListener("durationchange", setDuration);

+ 19 - 0
src/redux/actions/types.js

@@ -0,0 +1,19 @@
+
+const AUDIO = {
+    PLAY: "PLAY",
+    PAUSE: "PAUSE",
+    TOGGLE_PLAY: "TOGGLE_PLAY",
+    SET_DURATION: "SET_DURATION",
+    SET_CURRENT_TIME: "SET_CURRENT_TIME",
+    SET_VOLUME: "SET_VOLUME",
+    TOGGLE_REPEAT: "TOGGLE_REPEAT",
+    SHUFFLE: "SHUFFLE", // shuffle(all tracks, playlist, queue)
+    SET_TRACK: "SET_TRACK",
+    SET_PLAYLIST: "SET_PLAYLIST",
+    NEXT_TRACK: "NEXT_TRACK",
+    PREV_TRACK: "PREV_TRACK",
+}
+
+export default {
+    ...AUDIO,
+}

+ 42 - 0
src/redux/reducers/audioReducer.js

@@ -0,0 +1,42 @@
+import types from "../actions/types";
+
+const initialState = {
+    isPlaying: false,
+    duration: 0,
+    currentTime: 0,
+    volume: 1,
+    isRepeated: false,
+    isShuffled: false,
+    track: "",
+    playlist: {},
+};
+
+const playerReducer = (state = initialState, action) => {
+    switch (action.type) {
+        case types.TOGGLE_PLAY:
+            return { ...state, isPlaying: action.payload };
+
+        case types.PLAY:
+            return { ...state, isPlaying: true };
+
+        case types.PAUSE:
+            return { ...state, isPlaying: false };
+
+        case types.SET_DURATION:
+            return { ...state, duration: action.payload };
+
+        case types.SET_CURRENT_TIME:
+            return { ...state, currentTime: action.payload };
+
+        case types.SET_VOLUME:
+            return { ...state, volume: action.payload };
+
+        case types.TOGGLE_REPEAT:
+            return { ...state, isRepeated: action.payload };
+
+        default:
+            return state;
+    }
+};
+
+export default playerReducer;

+ 9 - 0
src/redux/reducers/index.js

@@ -0,0 +1,9 @@
+import {combineReducers} from "redux";
+
+import audioReducer from "./audioReducer";
+
+const rootReducers = combineReducers({
+    audio: audioReducer,
+})
+
+export default rootReducers;

+ 7 - 0
src/redux/store.js

@@ -0,0 +1,7 @@
+import { createStore, applyMiddleware } from "redux";
+import thunk from 'redux-thunk';
+import rootReducers from "./reducers";
+
+const store = createStore(rootReducers, applyMiddleware());
+
+export default store;

+ 1 - 1
src/utils/history.js

@@ -2,5 +2,5 @@ import { createBrowserHistory } from "history";
 
 export const history = createBrowserHistory();
 export const push = (location) => history.push(location);
-export const back = () => history.back();
+export const back = () => history.go(-2);
 export const forward = () => history.forward();