瀏覽代碼

start make reducer

mfdok43 2 年之前
父節點
當前提交
303df48e3d

+ 153 - 0
package-lock.json

@@ -20,6 +20,7 @@
         "react-router-dom": "^5.3.0",
         "react-scripts": "5.0.0",
         "redux": "^4.1.2",
+        "redux-saga": "^1.1.3",
         "redux-thunk": "^2.4.1",
         "web-vitals": "^2.1.2"
       }
@@ -2729,6 +2730,53 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@redux-saga/core": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
+      "integrity": "sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==",
+      "dependencies": {
+        "@babel/runtime": "^7.6.3",
+        "@redux-saga/deferred": "^1.1.2",
+        "@redux-saga/delay-p": "^1.1.2",
+        "@redux-saga/is": "^1.1.2",
+        "@redux-saga/symbols": "^1.1.2",
+        "@redux-saga/types": "^1.1.0",
+        "redux": "^4.0.4",
+        "typescript-tuple": "^2.2.1"
+      }
+    },
+    "node_modules/@redux-saga/deferred": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.1.2.tgz",
+      "integrity": "sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ=="
+    },
+    "node_modules/@redux-saga/delay-p": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.1.2.tgz",
+      "integrity": "sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==",
+      "dependencies": {
+        "@redux-saga/symbols": "^1.1.2"
+      }
+    },
+    "node_modules/@redux-saga/is": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.2.tgz",
+      "integrity": "sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==",
+      "dependencies": {
+        "@redux-saga/symbols": "^1.1.2",
+        "@redux-saga/types": "^1.1.0"
+      }
+    },
+    "node_modules/@redux-saga/symbols": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.2.tgz",
+      "integrity": "sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ=="
+    },
+    "node_modules/@redux-saga/types": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
+      "integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
+    },
     "node_modules/@rollup/plugin-babel": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -14583,6 +14631,14 @@
         "@babel/runtime": "^7.9.2"
       }
     },
+    "node_modules/redux-saga": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
+      "integrity": "sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==",
+      "dependencies": {
+        "@redux-saga/core": "^1.1.3"
+      }
+    },
     "node_modules/redux-thunk": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
@@ -16552,6 +16608,27 @@
         "node": ">=4.2.0"
       }
     },
+    "node_modules/typescript-compare": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
+      "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
+      "dependencies": {
+        "typescript-logic": "^0.0.0"
+      }
+    },
+    "node_modules/typescript-logic": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
+      "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
+    },
+    "node_modules/typescript-tuple": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
+      "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
+      "dependencies": {
+        "typescript-compare": "^0.0.2"
+      }
+    },
     "node_modules/unbox-primitive": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@@ -19578,6 +19655,53 @@
         }
       }
     },
+    "@redux-saga/core": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
+      "integrity": "sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==",
+      "requires": {
+        "@babel/runtime": "^7.6.3",
+        "@redux-saga/deferred": "^1.1.2",
+        "@redux-saga/delay-p": "^1.1.2",
+        "@redux-saga/is": "^1.1.2",
+        "@redux-saga/symbols": "^1.1.2",
+        "@redux-saga/types": "^1.1.0",
+        "redux": "^4.0.4",
+        "typescript-tuple": "^2.2.1"
+      }
+    },
+    "@redux-saga/deferred": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.1.2.tgz",
+      "integrity": "sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ=="
+    },
+    "@redux-saga/delay-p": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.1.2.tgz",
+      "integrity": "sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==",
+      "requires": {
+        "@redux-saga/symbols": "^1.1.2"
+      }
+    },
+    "@redux-saga/is": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.2.tgz",
+      "integrity": "sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==",
+      "requires": {
+        "@redux-saga/symbols": "^1.1.2",
+        "@redux-saga/types": "^1.1.0"
+      }
+    },
+    "@redux-saga/symbols": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.2.tgz",
+      "integrity": "sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ=="
+    },
+    "@redux-saga/types": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
+      "integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
+    },
     "@rollup/plugin-babel": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -28170,6 +28294,14 @@
         "@babel/runtime": "^7.9.2"
       }
     },
+    "redux-saga": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
+      "integrity": "sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==",
+      "requires": {
+        "@redux-saga/core": "^1.1.3"
+      }
+    },
     "redux-thunk": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
@@ -29659,6 +29791,27 @@
       "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
       "peer": true
     },
+    "typescript-compare": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
+      "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
+      "requires": {
+        "typescript-logic": "^0.0.0"
+      }
+    },
+    "typescript-logic": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
+      "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
+    },
+    "typescript-tuple": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
+      "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
+      "requires": {
+        "typescript-compare": "^0.0.2"
+      }
+    },
     "unbox-primitive": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "react-router-dom": "^5.3.0",
     "react-scripts": "5.0.0",
     "redux": "^4.1.2",
+    "redux-saga": "^1.1.3",
     "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.2"
   },

+ 6 - 5
src/App.scss

@@ -11,18 +11,18 @@
   display: flex;
   justify-content: space-between;
   background: #fc0874;
-height: 120px;
+height: 100px;
 }
 
 main {
   width: 99%;
   display: flex;
   flex-direction: row;
-  aside {
-    width: 20%;
-    background-color: cyan;
-  }
+}
 
+.Aside {
+  width: 20%;
+  background-color: cyan;
 }
 
 .Avatar {
@@ -78,3 +78,4 @@ footer {
 }
 
 
+

+ 115 - 39
src/actions/index.js

@@ -1,6 +1,11 @@
-import {actionPromise} from '../reducers'
-import {actionAuthLogin, actionAuthLogout} from '../reducers'
-import {store} from "../reducers";
+import {actionPromise, store} from '../reducers'
+import {actionAuthLogin} from '../reducers'
+import {all, takeEvery, put, call,select} from 'redux-saga/effects';
+import {promiseWorker} from "../reducers/promiseReducer";
+import jwtDecode from "jwt-decode";
+
+
+
 
 const getGQL = url =>
     async (query, variables = {}) => {
@@ -38,26 +43,43 @@ export const actionLogin = (login, password) => {
 
 
 export const actionFullLogin = (login, password) =>
-    async function i(dispatch) {
-        let token = await dispatch(actionLogin(login, password));
-        if (token) {
-            dispatch(actionAuthLogin(token));
-            dispatch(actionAboutMe())
-            dispatch(actionUserFind())
-            dispatch(actionFindMyTracks())
-        }
+    ({type:'FULL_LOGIN', login, password})
+
+export function* loginWorker (action) {
+    const {login, password} = action
+
+    let token = yield call(promiseWorker, actionLogin(login, password)) //dispatch(actionLogin(login, password));
+    if (token) {
+        yield put(actionAuthLogin(token));
+        yield put(actionUserFind())
+        yield put(actionFindMyTracks())
+        yield put(actionAboutMe())
+
     }
 
+}
+
+
+export function* loginWatcher() {
+    yield takeEvery ('FULL_LOGIN',loginWorker)
+}
+
+
 
 export const actionFullReg = (login, password) =>
-    async function a(dispatch) {
-        try {
-            await dispatch(actionReg(login, password));
-        } catch (e) {
-            return 0;
-        }
-        await dispatch(actionFullLogin(login, password));
+    ({type:'FULL_REG', login, password})
+
+function* regWorker(action) {
+    const {login, password} = action
+    let regId = yield call(promiseWorker, actionReg(login, password))
+    if (regId) {
+        yield put(actionFullLogin(login, password))
     }
+}
+
+export function* regWatcher() {
+    yield takeEvery('FULL_REG', regWorker)
+}
 
 export const actionReg = (login, password) => {
     return actionPromise(
@@ -81,22 +103,57 @@ export const actionUserFind = () => actionPromise('userFind', gql(`query {
         }`))
 
 
-export const actionTrackFindByOwner = (_id) => actionPromise('trackFindByOwner1', gql(`query trackFindByOwner($q: String){
+export const actionTrackFindByOwner = (myid) => actionPromise('trackFindByOwner1', gql(`query trackFindByOwner($q: String){
             TrackFind(query: $q){
                  _id url originalFileName
             }
-        }`, { q: JSON.stringify([{ ___owner: _id }]) }))
+        }`, { q: JSON.stringify([{ ___owner: myid }]) }))
+
 
 
 export const actionFindMyTracks = () =>
-    actionPromise('findMyTracks1', gql(`query findMyTracks($q: String){
+    ({type:'FIND_MY_TRACKS'})
+
+export function* findMyTracksWorker () {
+    let { auth } = yield select();
+    let userId = auth?.payload?.sub?.id;
+    yield call(
+        promiseWorker, actionPromise('findMyTracks1', gql(`query findMyTracks($q: String){
             TrackFind(query: $q){
                  _id url originalFileName
             }
-        }`, { q: JSON.stringify([{ ___owner: "61cda244e9472933a6785efc" }]) }))
+        }`, { q: JSON.stringify([{ ___owner: userId }]) }))
+    );
+
+}
 
+export function* findMyTracksWatcher() {
+    yield takeEvery ('FIND_MY_TRACKS',findMyTracksWorker)
+}
+
+
+
+
+
+
+
+export const actionTracksFind = () =>
+    actionPromise('findAllTracks1', gql(`query findAllTracks {
+    TrackFind (query: "[{}]"){
+        _id url originalFileName
+    }
+}`))
+
+
+
+
+export const actionPlaylistFindByOwner = () =>
+    actionPromise('playlistFindByOwner1', gql(`query playlistFindByOwner ($u: String) {
+    PlaylistFind(query: $u) {
+        _id name 
+    }
+}`, { q: JSON.stringify([{ ___owner: '61cda244e9472933a6785efc' }]) }))
 
-// const CactionFindMyTracks = connect (state => ({myId: state.auth?.payload.sub.id}))(actionFindMyTracks)
 
 
 export const actionUserFindOne = (_id, name = "userFindOne") =>
@@ -113,21 +170,31 @@ export const actionUserFindOne = (_id, name = "userFindOne") =>
     );
 
 
-export const actionAboutMe = () => async (dispatch, getState) => {
-    let { auth } = getState();
+export const actionAboutMe = () =>
+    ({type:'ABOUT_ME'})
+
+export function* aboutMeWorker () {
+    let { auth } = yield select();
     let id = auth?.payload?.sub.id;
     if (id) {
-        await dispatch(actionUserFindOne(id, "aboutMe"));
+        yield put(actionUserFindOne(id, "aboutMe"));
     }
 };
 
+export function* aboutMeWatcher() {
+    yield takeEvery ('ABOUT_ME',aboutMeWorker)
+}
 
-export const actionUploadImage = (file) => {
+
+
+
+
+export const actionUploadTrack = (file) => {
     let fd = new FormData();
-    fd.append("photo", file);
+    fd.append("track", file);
     return actionPromise(
         "uploadFile",
-        fetch('http://player.asmer.fs.a-level.com.ua/upload', {
+        fetch(backURL+'/track', {
             method: "POST",
             headers: localStorage.authToken
                 ? { Authorization: "Bearer " + localStorage.authToken }
@@ -137,12 +204,13 @@ export const actionUploadImage = (file) => {
     );
 };
 
-export const actionUploadTrack = (file) => {
+
+export const actionUploadImage = (file) => {
     let fd = new FormData();
-    fd.append("track", file);
+    fd.append("photo", file);
     return actionPromise(
         "uploadFile",
-        fetch('http://player.asmer.fs.a-level.com.ua/track', {
+        fetch(backURL+'/upload', {
             method: "POST",
             headers: localStorage.authToken
                 ? { Authorization: "Bearer " + localStorage.authToken }
@@ -152,13 +220,18 @@ export const actionUploadTrack = (file) => {
     );
 };
 
-export const actionSetAvatar = (file) => async (dispatch, getState) => {
-    let result = await dispatch(actionUploadImage(file));
-    let { auth } = getState();
+
+export const actionSetAvatar = (file) =>
+    ({type:'SET_AVATAR', file})
+
+export function* setAvatarWorker (action) {
+    const {file} = action
+    let result = yield call(promiseWorker,actionUploadImage(file));
+    let { auth } = yield select();
     let imageId = result._id;
     let userId = auth?.payload?.sub?.id;
-    await dispatch(
-        actionPromise(
+    yield call(
+        promiseWorker,actionPromise(
             "setAvatar",
             gql(
                 `mutation setAva($userId: String, $imageId: ID){
@@ -167,7 +240,10 @@ export const actionSetAvatar = (file) => async (dispatch, getState) => {
             )
         )
     );
-    await dispatch(actionAboutMe());
+    yield put(actionAboutMe());
 };
 
-store.dispatch(actionAboutMe())
+export function* setAvatarWatcher() {
+    yield takeEvery ('SET_AVATAR',setAvatarWorker)
+}
+

二進制
src/ava-def.jpg


+ 17 - 0
src/pages/allTracks.js

@@ -0,0 +1,17 @@
+import {actionTracksFind, actionUserFind, backURL} from "../actions";
+import {connect} from "react-redux";
+import {store} from "../reducers";
+
+const Track = ({track:{_id,url,originalFileName}={}}) =>
+    <div className='Tracks'>
+        {url === null ? <></> :(<strong className='Tracks1'><audio controls src={backURL+'/'+url}></audio>{originalFileName}</strong>)}
+    </div>
+
+const AllTracks = ({tracks}={}) =>
+    <div>
+        {(tracks || []).map(track => <Track track={track}/>)}
+    </div>
+
+
+export const CAllTracks = connect(state => ({tracks: state.promise.findAllTracks1?.payload || []}))(AllTracks)
+store.dispatch(actionTracksFind())

+ 9 - 9
src/pages/aside.js

@@ -115,25 +115,25 @@ const defaultPlaylists = [
 
 
 
-const Playlist = ({playlist:{_id, login}={}}) =>
+const User = ({playlist:{_id, login}={}}) =>
 <li><Link to={`/playlist/${_id}`}>{login}</Link></li>
 
 
-const Playlists = ({playlists=defaultPlaylists}) =>{
+const AllUsers = ({playlists=defaultPlaylists}) =>{
+
         return (
+            <div>
+                <h3>Плейлисты ползователей</h3>
             <ul className='Users'>
-                    {playlists.map(playlist =>  <Playlist playlist={playlist}/> )}
+                    {playlists.map(playlist =>  <User playlist={playlist}/> )}
             </ul>
+            </div>
         )
 
 }
 
-const CPlaylists = connect(state => ({playlists: state.promise.userFind?.payload || []}))
-(Playlists)
+export const CAllUsers = connect(state => ({playlists: state.promise.userFind?.payload || []}))
+(AllUsers)
 
-export const Aside = () =>
-    <aside>
-            <CPlaylists />
-    </aside>
 
 store.dispatch(actionUserFind())

+ 0 - 4
src/pages/content.js

@@ -1,4 +0,0 @@
-export const Content = ({children}) =>
-    <div>
-        {children}
-    </div>

+ 9 - 7
src/pages/header/avatar.js

@@ -3,14 +3,15 @@ import {store} from "../../reducers";
 import {actionSetAvatar, actionUploadImage, backURL} from "../../actions";
 import {useDropzone} from "react-dropzone";
 import {connect} from "react-redux";
+import avadef from "../../ava-def.jpg";
+import {all, takeEvery, put, call} from 'redux-saga/effects';
 
-function AvatarDropzone({ onLoad }) {
+
+function AvatarDropzone({onLoad}) {
     const onDrop = useCallback((acceptedFiles) => {
         // Do something with the files
         onLoad(acceptedFiles[0]);
-
-        store.dispatch(actionSetAvatar(acceptedFiles[0]));
-
+        store.dispatch(actionSetAvatar(acceptedFiles[0]))
     }, []);
     const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
 
@@ -18,7 +19,7 @@ function AvatarDropzone({ onLoad }) {
         <div {...getRootProps()}>
             <input {...getInputProps()} />
             {isDragActive ? (
-                <p>Перетащите файл сюда ...</p>
+                <p className=''>Перетащите файл сюда ...</p>
             ) : (
                 <p>Сменить аву, нажмите или перетащите файл</p>
             )}
@@ -30,11 +31,12 @@ function AvatarDropzone({ onLoad }) {
 export const CAvatarDropZone = connect (null, {onLoad: actionUploadImage}) (AvatarDropzone)
 
 
+
 const Avatar = ({ avatarURL = {} }) => {
-    console.log(avatarURL);
+    // console.log(avatarURL);
     return (
         <div className='Avatar'>
-            <img src={backURL + "/" + avatarURL.avatar?.url}></img>
+            {avatarURL.avatar?.url !== null ? <img src={backURL + "/" + avatarURL.avatar?.url}></img> : <img src={avadef}/>}
         </div>
     )
 };

+ 50 - 1
src/pages/header/header-build.js

@@ -1,16 +1,64 @@
 import {Link, Route, Switch} from "react-router-dom";
 import {connect} from "react-redux";
-import {actionAuthLogout} from "../../reducers";
+import {actionAuthLogin, actionAuthLogout, actionPromise} from "../../reducers";
 import {CLoginForm} from "./login";
 import {CRegForm} from "./registration";
 import {useState} from "react";
 import {CAvatar,CAvatarDropZone} from "./avatar";
 import {Logo} from "./logo";
+import {history} from "../../App";
+import {actionFullLogin, gql} from "../../actions";
+import {takeEvery, select, call,put} from "redux-saga/effects";
+import {promiseWorker} from "../../reducers/promiseReducer";
+
+
+
+
+export const actionSetUserPassword = (password) =>
+    ({type:'SET_USER_PASSWORD', password})
+
+export function* setUserPasswordWorker (action) {
+        let {password} = action
+        let {auth} = yield select()
+        let userId = auth.payload?.sub?.id
+        console.log(userId, password)
+        // yield put (actionAuthLogout())
+        yield call(promiseWorker,actionPromise('register', gql(`mutation reg($user:UserInput) {
+         UserUpsert(user:$user) {
+         _id 
+         }
+      }`, {user: {userId, password}})))
+        // yield put (actionFullLogin)
+}
+
+
+export function* setUserPasswordWatcher() {
+    yield takeEvery ('SET_USER_PASSWORD',setUserPasswordWorker)
+}
+
+const ChangePasswordForm = ({onChangePassword}) => {
+    const [p, setP] = useState ('')
+    return (
+        <div>
+            <input type='password' placeholder='Введите пароль' style={{backgroundColor:"skyblue"}} onChange={e => setP(e.target.value)}></input>
+            <button onClick={() => {onChangePassword(p); history.push('/')}}>Сменить пароль</button>
+        </div>
+    )
+}
+
+export const CChangePasswordForm = connect(null,{onChangePassword:actionSetUserPassword})(ChangePasswordForm)
+
+
+
+
 
 
 export const Header = () =>
     <header className="Header">
         <Logo />
+        <Link to={`/music`}><h2>Все песни</h2></Link>
+        <Link to={`/mymusic`}> <h2>Моя музыка</h2></Link>
+        <h2>Поиск🔎</h2>
         <Switch>
             <Route path="/login" component={CLoginForm}/>
             <Route path="/registration" component={CRegForm}/>
@@ -47,6 +95,7 @@ const LoginButtons = ({onLogout, history, token}) => {
                     <Spoiler header={<CAvatar/>}>
                         <strong>{JSON.parse(atob(token.split(".")[1])).sub.login}</strong>
                         <CAvatarDropZone />
+                        <CChangePasswordForm/>
                         <button onClick={() => {onLogout(); history.push('/')}}>Выйти</button>
                     </Spoiler>
                 </div>)

+ 2 - 2
src/pages/index.js

@@ -3,5 +3,5 @@ export {Main} from "./main";
 export {Footer} from "./footer";
 export {PageMain} from "./pageMain";
 export {UserTracks} from "./userTracks";
-export {Aside} from "./aside";
-export {Content} from "./content";
+export {CAllUsers} from "./allUsers";
+export {CMyPlaylists} from "./myPlaylists";

+ 18 - 3
src/pages/main.js

@@ -1,19 +1,34 @@
 import {Route, Redirect, Switch} from 'react-router-dom';
-import {Content} from "./content";
 import {PageMain} from "./pageMain";
 import {CUserTracks} from "./userTracks";
-import {Aside} from "./aside";
+import {CAllTracks} from "./allTracks";
+import {CAllUsers} from "./allUsers";
+import {CMyPlaylists} from "./myPlaylists";
 
+export const Aside = ({children}) =>
+    <div className='Aside'>
+        {children}
+    </div>
 
+export const Content = ({children}) =>
+    <div>
+        {children}
+    </div>
 
 export const Main = () =>
     <main>
-        <Aside />
+        <Aside>
+            <Route path="/music" component={CAllUsers}/>
+            <Route path="/mymusic" component={CMyPlaylists}/>
+            <Route path='/' component={CAllUsers} exact/>
+        </Aside>
         <Content>
             <Redirect from='/main' to='/'/>
             <Switch>
+                <Route path="/music" component={CAllTracks}/>
                 <Route path='/' component={PageMain} exact/>
                 <Route path="/playlist/:_id" component={CUserTracks}/>
+                <Route path='/mymusic' component={PageMain}/>
             </Switch>
         </Content>
    </main>

+ 45 - 0
src/pages/myPlaylists.js

@@ -0,0 +1,45 @@
+import {actionPlaylistFindByOwner, gql, setAvatarWorker} from "../actions";
+import {connect} from "react-redux";
+import {Link} from "react-router-dom";
+import {call, select, takeEvery} from "redux-saga/effects";
+import {actionPromise, promiseWorker} from "../reducers/promiseReducer";
+import {useState} from "react";
+import {history} from "../App";
+
+
+export const actionCreatePlaylist = (name) =>
+    ({type:'CREATE_PLAYLIST', name})
+
+export function* createPlaylistWorker (action) {
+    let {name} = action
+    let {auth} = yield select()
+    let userId = auth.payload?.sub?.id
+    yield call(promiseWorker,actionPromise('createPlaylist', gql(`mutation reg($playlist:PlaylistInput) {
+         PlaylistUpsert(playlist:$playlist) {
+         _id 
+         }
+      }`, {playlist: {userId, name}})))
+}
+
+
+export function* createPlaylistWatcher() {
+    yield takeEvery ('CREATE_PLAYLIST',createPlaylistWorker)
+}
+
+const Playlist = ({playlist:{_id, name}={}}) =>
+    <li><Link to={`/playlist/${_id}`}>{name}</Link></li>
+
+const MyPlaylists =  ({playlists={},onCreatePlaylist}) => {
+    const [p, setP] = useState ('')
+return (
+    <div>
+    <input placeholder='Название' onChange={e => setP(e.target.value)}/>
+        <button onClick={() => {onCreatePlaylist(p)}}>Создать плейлист</button>
+    <ul className='Users'>
+        {playlists.map(playlist =>  <Playlist playlist={playlist}/> )}
+    </ul>
+    </div>
+)
+}
+
+export const CMyPlaylists = connect(state => ({playlists: state.promise.playlistFindByOwner1?.payload || []}),{onCreatePlaylist:actionCreatePlaylist})(MyPlaylists)

+ 4 - 24
src/pages/pageMain.js

@@ -18,7 +18,7 @@ const defaultTrack = {
 
 export const PageMain = () =>
     <div>
-    <h1>Главная страница</h1>
+    <h1>Моя музыка</h1>
         <CTrackDropZone />
         <CPlayer />
     </div>
@@ -39,8 +39,8 @@ function TrackDropZone({ onLoad }) {
                 <p>Для добавления трэка перетащите файлы в плейлист</p>
 
             )}
-           {/*<CPreloaded>*/}
-           {/*    <CMyTracks />*/}
+           {/*<CPreloaded name='findMyTracks1'>*/}
+           {/*   <CMyTracks />*/}
            {/*</CPreloaded>*/}
 
         </div>
@@ -51,25 +51,5 @@ function TrackDropZone({ onLoad }) {
 
 const CTrackDropZone = connect (null, {onLoad: actionUploadTrack}) (TrackDropZone)
 
-
-
-
-const Track = ({track:{url,originalFileName}={}}) => {
-    return (
-        <div className='Tracks'>
-            <audio controls src={backURL+'/'+url}></audio>
-            <strong>{originalFileName}</strong></div>
-    )
-}
-
-const MyTracks = ({tracks=defaultTrack}) => {
-    return(
-        <div>
-            {console.log(tracks,'kak dela')}
-        {tracks.map(track => <Track track={track}/> )}
-        </div>
-    )
-}
-const CMyTracks = connect(state => ({tracks: state.promise.findMyTracks1?.payload || []}))(MyTracks)
-
 store.dispatch(actionFindMyTracks())
+

+ 41 - 15
src/pages/player.js

@@ -1,7 +1,7 @@
 import {useCallback,useState,useEffect,useRef} from "react";
 import play from '../play.svg'
 import pause from '../pause.svg'
-import {store} from "../reducers";
+import {store,actionTrackPlay,actionTrackStop} from "../reducers";
 import {connect} from "react-redux";
 import {actionFindMyTracks, backURL} from "../actions";
 
@@ -37,28 +37,19 @@ const defaultTracks = [
 
 
 //<audio controls src={backURL+'/'+url}></audio>
-const Track = ({track:{id,url,originalFileName}}) => {
-
+const Track = ({track:{id,url,originalFileName},trackPlay,trackStop}) => {
+     let audio = new Audio()
     let audioSrc = backURL + '/'+ url
     const [isPlaying, setIsPlaying] = useState(false);
-    const audioRef = useRef(new Audio(audioSrc));
-
-    useEffect(() => {
-        if (isPlaying) {
-            audioRef.current.play();
-        } else {
-            audioRef.current.pause();
-        }
-    }, [isPlaying]);
 
     return (
         <div className='Tracks'>
             {isPlaying ? (
-                <button onClick={() => setIsPlaying(false)}>
+                <button onClick={() => trackPlay({audio})}>
                     <img src={pause}/>
                 </button>
             ) : (
-                <button onClick={() => setIsPlaying(true)}>
+                <button onClick={() => trackStop({audio})}>
                     <img src={play}/>
                 </button>
             )}
@@ -67,6 +58,41 @@ const Track = ({track:{id,url,originalFileName}}) => {
     )
 }
 
+export const CTrack = connect(null, {trackPlay: actionTrackPlay, trackStop: actionTrackStop})(Track)
+
+// const Track = ({track:{id,url,originalFileName}}) => {
+//
+//     let audioSrc = backURL + '/'+ url
+//     const [isPlaying, setIsPlaying] = useState(false);
+//     const audioRef = useRef(new Audio(audioSrc));
+//
+//     useEffect(() => {
+//         if (isPlaying) {
+//             audioRef.current.play();
+//         } else {
+//             audioRef.current.pause();
+//         }
+//     }, [isPlaying]);
+//
+//     return (
+//         <div className='Tracks'>
+//             {isPlaying ? (
+//                 <button onClick={() => setIsPlaying(false)}>
+//                     <img src={pause}/>
+//                 </button>
+//             ) : (
+//                 <button onClick={() => setIsPlaying(true)}>
+//                     <img src={play}/>
+//                 </button>
+//             )}
+//             <span>{originalFileName}</span>
+//         </div>
+//     )
+// }
+
+
+
+
 const Player = ({tracks=defaultTracks}) => {
 
     return(
@@ -77,4 +103,4 @@ const Player = ({tracks=defaultTracks}) => {
 }
 export const CPlayer = connect(state => ({tracks: state.promise.findMyTracks1?.payload || []}))(Player)
 
-store.dispatch(actionFindMyTracks())
+

src/pages/preloader/preloader1.css → src/pages/preloader/preloader.css


+ 3 - 3
src/pages/preloader/preloader.js

@@ -1,9 +1,9 @@
 import logo from "../../logo.svg";
-import preloader1 from './preloader1.css'
+import preloader from './preloader.css'
 import {connect} from "react-redux";
 
 
-export const Preloader1 = () =>
+export const Preloader = () =>
     <div className="loadingio-spinner-eclipse-leb3x7lyjtj">
         <div className="ldio-vhhfhwlovld">
             <div></div>
@@ -21,7 +21,7 @@ export const Preloaded = ({promiseName, promiseState, children}) =>
         {promiseState[promiseName]?.status === 'RESOLVED' ? children :
             promiseState[promiseName]?.status === 'REJECTED' ?
                 <RejectAlert error={promiseState[promiseName]?.error}/>:
-                <Preloader1/>}
+                <Preloader/>}
     </>
 
 export const CPreloaded = connect(state => ({promiseState: state.promise}))(Preloaded)

+ 1 - 1
src/pages/userTracks.js

@@ -50,7 +50,7 @@ export const UserTracks = ({match:{params:{_id}}, getData}, history) => {
         getData(_id)
     }, [_id])
     return (
-        // <CPreloaded>
+       // <CPreloaded name='trackFindByOwner1'>
         <CTracks/>
         // </CPreloaded>
     )}

+ 2 - 0
src/reducers/authReducer.js

@@ -25,5 +25,7 @@ export function authReducer(state, { type, token }) {
     return state
 }
 
+
+
 export const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
 export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })

+ 1 - 0
src/reducers/index.js

@@ -1,3 +1,4 @@
 export {store} from './store'
 export {actionPromise} from './promiseReducer'
+export {playerReducer,actionTrackPlay,actionTrackStop} from "./playerReducer";
 export {actionAuthLogin, actionAuthLogout} from './authReducer'

+ 30 - 2
src/reducers/playerReducer.js

@@ -1,4 +1,32 @@
-// export function playerReducer(state={},{type,audio,duration=0}) {}
-export function playerReducer(state,action) {
+export function playerReducer(state={},{
+    type,
+    isPlaying=false,
+    isStopped,
+    duration,
+    track,
+    // playlist: {_id, name, tracks},
+    playlistIndex,
+    currentTime=0,
+    volume,
+}) {
 
+    if (type === 'TRACK_PLAY') {
+        return {
+            ...state,
+            isPlaying,
+            isStopped: !isPlaying,
+        };
+    }
+    if (type === 'TRACK_STOP') {
+        return {
+            ...state,
+            isStopped,
+            isPlaying: !isStopped,
+        };
+    }
+
+    return state
 }
+
+export const actionTrackPlay = (track,isPlaying) => ({type: "TRACK_PLAY",isPlaying: track.play()})
+export const actionTrackStop = (track,isStopped) => ({type: "TRACK_PLAY",isStopped: track.pause()})

+ 21 - 11
src/reducers/promiseReducer.js

@@ -1,3 +1,5 @@
+import {all, takeEvery, put, call} from 'redux-saga/effects';
+
 export function promiseReducer(state = {}, { type, status, payload, error, name }) {
     if (type === 'PROMISE') {
         return {
@@ -13,14 +15,22 @@ const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED'
 const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
 
 export const actionPromise = (name, promise) =>
-    async dispatch => {
-        dispatch(actionPending(name))
-        try {
-            let data = await promise
-            dispatch(actionResolved(name, data))
-            return data
-        }
-        catch (error) {
-            dispatch(actionRejected(name, error))
-        }
-    }
+    ({type: 'PROMISE_START', name, promise})
+
+export function* promiseWorker(action){ //это типа actionPromise который thunk
+    const {name, promise} = action
+    yield put(actionPending(name))
+    try {
+        let data = yield promise
+        yield put(actionResolved(name, data))
+        return data
+    }
+    catch (error) {
+        yield put(actionRejected(name, error))
+    }
+}
+
+
+export function* promiseWatcher(){
+    yield takeEvery('PROMISE_START', promiseWorker)
+}

+ 38 - 4
src/reducers/store.js

@@ -1,12 +1,46 @@
 import {applyMiddleware, combineReducers, createStore} from "redux";
 import {authReducer} from "./authReducer";
-import {promiseReducer} from "./promiseReducer";
+import {promiseReducer,promiseWatcher} from "./promiseReducer";
+import {playerReducer} from "./playerReducer";
+import {
+    loginWatcher,
+    regWatcher,
+    setAvatarWatcher,
+    aboutMeWatcher,
+    findMyTracksWatcher,
+    actionAboutMe,
+    actionFindMyTracks
+} from "../actions";
 import {localStoredReducer} from "./localStoredReducer";
-import thunk from "redux-thunk";
+import createSagaMiddleware from 'redux-saga';
+import {all} from 'redux-saga/effects';
+import {createPlaylistWatcher} from "../pages/myPlaylists";
+// import {setUserPasswordWatcher} from "../pages/header/header-build";
 
+const sagaMiddleware = createSagaMiddleware()
 export const store = createStore(combineReducers({promise: localStoredReducer(promiseReducer, 'promise'),
-                                                             auth: localStoredReducer(authReducer, 'auth')}),
-                                                                   applyMiddleware(thunk))
+                                                             auth: localStoredReducer(authReducer, 'auth'),
+                                                           player: localStoredReducer(playerReducer, 'player')}),
+                                                              applyMiddleware(sagaMiddleware))
 
+// export const store = createStore(combineReducers({promise: promiseReducer,
+//         auth: authReducer}),
+//     applyMiddleware(sagaMiddleware))
+
+
+function* rootSaga(){
+    yield all([
+        promiseWatcher(),
+         loginWatcher(),
+         regWatcher(),
+         setAvatarWatcher(),
+         aboutMeWatcher(),
+         createPlaylistWatcher(),
+        findMyTracksWatcher(),
+         // setUserPasswordWatcher(),
+    ])
+}
+store.dispatch(actionAboutMe())
+sagaMiddleware.run(rootSaga)
 store.subscribe(() => console.log(store.getState()))