Jelajahi Sumber

Almost working player and bulk uploading tracks to a playlist

surstrommed 3 tahun lalu
induk
melakukan
5aee688236

File diff ditekan karena terlalu besar
+ 17649 - 44
package-lock.json


+ 68 - 68
src/App.js

@@ -26,7 +26,16 @@ import {
 import * as actions from "./actions";
 import { delay, gql } from "./helpers";
 import { CAudioController } from "./components/AudioController";
-
+import {
+  audioLoadWatcher,
+  audioPlayWatcher,
+  audioPauseWatcher,
+  audioPrevTrackWatcher,
+  audioNextTrackWatcher,
+  audioSetDurationWatcher,
+  audioSetVolumeWatcher,
+  audioSetCurrentTimeWatcher,
+} from "./components/Audio";
 export const history = createBrowserHistory();
 
 const sagaMiddleware = createSagaMiddleware();
@@ -46,6 +55,7 @@ export const store = createStore(
 function* rootSaga() {
   yield all([
     promiseWatcher(),
+    aboutAnotherUserWatcher(),
     aboutMeWatcher(),
     routeWatcher(),
     loginWatcher(),
@@ -59,8 +69,12 @@ function* rootSaga() {
     searchWatcher(),
     audioPlayWatcher(),
     audioPauseWatcher(),
-    audioVolumeWatcher(),
+    audioPrevTrackWatcher(),
+    audioNextTrackWatcher(),
+    audioSetCurrentTimeWatcher(),
+    audioSetVolumeWatcher(),
     audioLoadWatcher(),
+    audioSetDurationWatcher(),
     findPlaylistByOwnerWatcher(),
     createPlaylistWatcher(),
     findPlaylistTracksWatcher(),
@@ -101,6 +115,14 @@ function* aboutMeWatcher() {
   yield takeEvery("ABOUT_ME", aboutMeWorker);
 }
 
+function* aboutAnotherUserWorker({ id }) {
+  yield call(promiseWorker, actions.actionFindUser(id, "anotherUser"));
+}
+
+function* aboutAnotherUserWatcher() {
+  yield takeEvery("ABOUT_ANOTHER_USER", aboutAnotherUserWorker);
+}
+
 if (localStorage.authToken) {
   store.dispatch(actions.actionAboutMe());
 }
@@ -158,12 +180,6 @@ function* registerWatcher() {
 }
 
 function* setAvatarWorker({ file }) {
-  // async (dispatch, getState) => {
-  //   let { _id } = await dispatch(actionUploadPhoto(file));
-  //   let { id } = getState().auth.payload.sub;
-  //   await dispatch(actionUserUpdate({ _id: id, avatar: { _id } }));
-  //   await dispatch(actionAboutMe());
-  // };
   let { _id } = yield call(promiseWorker, actions.actionUploadFile(file));
   let { auth } = yield select();
   if (auth) {
@@ -181,8 +197,6 @@ function* setAvatarWatcher() {
 }
 
 function* setNicknameWorker({ _id, nick }) {
-  //   await dispatch(actionUserUpdate({ _id, nick }));
-  //   await dispatch(actionAboutMe());
   yield call(promiseWorker, actions.actionUserUpdate({ _id, nick }));
   yield put(actions.actionAboutMe());
 }
@@ -203,8 +217,6 @@ function* setEmailWatcher() {
 }
 
 function* setNewPasswordWorker({ login, password, newPassword }) {
-  //   await dispatch(actionChangePassword(login, password, newPassword));
-  //   await dispatch(actionAboutMe());
   yield call(
     promiseWorker,
     actions.actionChangePassword(login, password, newPassword)
@@ -238,10 +250,7 @@ export function* searchWorker({ text }) {
     {
       query: JSON.stringify([
         {
-          $or: [
-            { originalFileName: `/${text}/` },
-            { owner: { login: `/${text}/` } },
-          ],
+          $or: [{ originalFileName: `/${text}/` }],
         },
         {
           sort: [{ name: 1 }],
@@ -250,46 +259,12 @@ export function* searchWorker({ text }) {
     }
   );
   yield put(actions.actionSearchResult({ payload }));
-  console.log("search end", text);
 }
 
 function* searchWatcher() {
   yield takeLatest("SEARCH", searchWorker);
 }
 
-function* audioLoadWorker({ track, duration, playlist, playlistIndex }) {
-  yield put(actions.actionLoadAudio(track, duration, playlist, playlistIndex));
-}
-
-function* audioLoadWatcher() {
-  yield takeEvery("LOAD_TRACK", audioLoadWorker);
-}
-
-function* audioPlayWorker({ isPlaying }) {
-  console.log("Play track");
-  yield put(actions.actionPlayAudio(isPlaying));
-}
-
-function* audioPlayWatcher() {
-  yield takeEvery("PLAY_TRACK", audioPlayWorker);
-}
-
-function* audioPauseWorker({ isPaused }) {
-  yield put(actions.actionPauseAudio(isPaused));
-}
-
-function* audioPauseWatcher() {
-  yield takeEvery("PAUSE_TRACK", audioPauseWorker);
-}
-
-function* audioVolumeWorker({ volume }) {
-  yield put(actions.actionVolumeAudio(volume));
-}
-
-function* audioVolumeWatcher() {
-  yield takeEvery("VOLUME_TRACK", audioVolumeWorker);
-}
-
 function* findPlaylistByOwnerWorker() {
   let { auth } = yield select();
   if (auth) {
@@ -326,6 +301,7 @@ function* findTracksWatcher() {
 }
 
 if (localStorage.authToken && history.location.pathname === "/search") {
+  console.log("Search");
   store.dispatch(actions.actionAllTracks());
 }
 
@@ -345,10 +321,10 @@ function* findPlaylistTracksWatcher() {
   yield takeEvery("PLAYLIST_TRACKS", findPlaylistTracksWorker);
 }
 
-function* loadTracksToPlaylistWorker({ idTrack, idPlaylist }) {
+function* loadTracksToPlaylistWorker({ idPlaylist, array }) {
   yield call(
     promiseWorker,
-    actions.actionLoadTracksToPlaylist(idTrack, idPlaylist)
+    actions.actionLoadTracksToPlaylist(idPlaylist, array)
   );
 }
 
@@ -356,18 +332,44 @@ function* loadTracksToPlaylistWatcher() {
   yield takeEvery("LOAD_TRACKS_PLAYLIST", loadTracksToPlaylistWorker);
 }
 
+if (
+  localStorage.authToken &&
+  history.location.pathname.includes("/myplaylist")
+) {
+  let { auth } = store.getState();
+  if (auth) {
+    let currentPlaylistId = history.location.pathname.substring(
+      history.location.pathname.lastIndexOf("/") + 1
+    );
+    store.dispatch(actions.actionPlaylistTracks(currentPlaylistId));
+  }
+}
+
+const uploadedTracks = [];
+
 export function* uploadTracksToPlaylistWorker({ file }) {
+  const currentTracksPlaylist = [];
+  let resultArray = [];
   let idPlaylist = history.location.pathname.substring(
-    history.location.pathname.lastIndexOf("/")
+    history.location.pathname.lastIndexOf("/") + 1
   );
+  let { promise } = yield select();
+  if (promise?.playlistTracks?.payload?.tracks) {
+    promise?.playlistTracks?.payload?.tracks.map((playlistTrack) =>
+      currentTracksPlaylist.push(playlistTrack)
+    );
+  }
   if (file) {
     let track = yield call(
       promiseWorker,
       actions.actionUploadFile(file, "track")
     );
-    let idTrack = track?._id;
-    console.log(idTrack, idPlaylist);
-    yield put(actions.actionTracksToPlaylist(idTrack, idPlaylist));
+    uploadedTracks.push(track);
+    let allUploadTracks = [...currentTracksPlaylist, ...uploadedTracks];
+    allUploadTracks.map((uploadedTrack) =>
+      resultArray.push({ _id: uploadedTrack?._id })
+    );
+    yield put(actions.actionTracksToPlaylist(idPlaylist, resultArray));
   }
   yield put(actions.actionAboutMe());
 }
@@ -376,22 +378,20 @@ export function* uploadTracksToPlaylistWatcher() {
   yield takeEvery("UPLOAD_TRACKS", uploadTracksToPlaylistWorker);
 }
 
-if (
-  localStorage.authToken &&
-  history.location.pathname.includes("/myplaylist")
-) {
-  let { auth } = store.getState();
-  if (auth) {
-    let { id } = auth?.payload?.sub;
-    store.dispatch(actions.actionPlaylistTracks(id));
-  }
-}
-
 if (localStorage.authToken && history.location.pathname === "/library") {
   store.dispatch(actions.actionFindUserPlaylists());
 }
 
-if (localStorage.authToken && history.location.pathname === "/") {
+if (localStorage.authToken && history.location.pathname.includes("/profile")) {
+  let currentUserId = history.location.pathname.substring(
+    history.location.pathname.lastIndexOf("/") + 1
+  );
+  let { auth } = store.getState();
+  if (auth) {
+    currentUserId === auth?.payload?.sub?.id
+      ? store.dispatch(actions.actionAboutMe())
+      : store.dispatch(actions.actionAboutAnotherUser(currentUserId));
+  }
 }
 
 store.subscribe(() => console.log(store.getState()));

+ 118 - 68
src/actions/index.js

@@ -63,9 +63,9 @@ export const actionRegister = (login, password) =>
     )
   );
 
-export const actionFindUser = (_id) =>
+export const actionFindUser = (_id, type = "myUser") =>
   actionPromise(
-    "user",
+    `${type}`,
     gql(
       `query findUser($q:String){
         UserFindOne(query:$q){
@@ -118,6 +118,27 @@ export const actionFindTracks = () =>
     )
   );
 
+export const actionFindUserTracks = (_id, type = "myTracks") =>
+  // поиск по айди пользователя его треков
+  actionPromise(
+    `${type}`,
+    gql(
+      `query findTracks($q:String){
+        TrackFind(query: $q) {
+          _id url originalFileName
+          id3 {
+              title, artist
+          }
+          playlists {
+              _id, name
+          }
+      }
+      }
+  `,
+      { q: JSON.stringify([{ ___owner: _id }]) }
+    )
+  );
+
 export const actionUserTracks = (_id) =>
   // поиск одного трека по его айди
   actionPromise(
@@ -150,7 +171,7 @@ export const actionUserPlaylists = (_id) =>
       `
           query getPlaylistByOwnerId($q:String!) {
               PlaylistFind(query: $q) {
-                _id name owner {login}
+                _id name owner {login} tracks {_id url originalFileName}
               }
           }
       `,
@@ -184,7 +205,7 @@ export const actionFindPlaylistTracks = (_id) =>
     )
   );
 
-export const actionLoadTracksToPlaylist = (idTrack, idPlaylist) =>
+export const actionLoadTracksToPlaylist = (idPlaylist, array) =>
   actionPromise(
     "loadTracksToPlaylist",
     gql(
@@ -193,7 +214,7 @@ export const actionLoadTracksToPlaylist = (idTrack, idPlaylist) =>
  _id name tracks {_id url originalFileName} owner {_id login}
  }
 }`,
-      { playlist: { _id: idPlaylist, tracks: { _id: idTrack } } }
+      { playlist: { _id: idPlaylist, tracks: array } }
     )
   );
 
@@ -212,6 +233,19 @@ export const actionUploadFile = (file, type = "photo") => {
   );
 };
 
+export const actionDeletePlaylist = (idPlaylist) =>
+  actionPromise(
+    "deletePlaylist",
+    gql(
+      `mutation deletePlaylist($playlist:PlaylistInput) {
+ PlaylistUpsert(playlist:$playlist) {
+ _id name tracks
+ }
+}`,
+      { playlist: { _id: idPlaylist } }
+    )
+  );
+
 // ================================================
 
 export const actionPending = (name) => ({
@@ -243,24 +277,15 @@ export const actionPromise = (name, promise) => ({
   name,
   promise,
 });
-// async (dispatch) => {
-//   dispatch(actionPending(name));
-//   try {
-//     let data = await promise;
-//     dispatch(actionResolved(name, data));
-//     return data;
-//   } catch (error) {
-//     dispatch(actionRejected(name, error));
-//   }
-// };
+
+export const actionAboutAnotherUser = (id) => ({
+  type: "ABOUT_ANOTHER_USER",
+  id,
+});
 
 export const actionAboutMe = () => ({
   type: "ABOUT_ME",
 });
-// => async (dispatch, getState) => {
-//   let { id } = getState().auth.payload.sub; //select()
-//   await dispatch(actionFindUser(id));       //call(promiseWatcher, actionFindUser())
-// };
 
 export const actionFullLogin = (login, password) => ({
   type: "FULL_LOGIN",
@@ -268,64 +293,28 @@ export const actionFullLogin = (login, password) => ({
   password,
 });
 
-// async (dispatch) => {
-//   let token = await dispatch(actionLogin(l, p));
-//   if (token) {
-//     await dispatch(actionAuthLogin(token));
-//     await dispatch(actionAboutMe());
-//   }
-// };
-
 export const actionFullRegister = (login, password) => ({
   type: "FULL_REGISTER",
   login,
   password,
 });
-// async (dispatch) => {
-//   let { _id } = await dispatch(actionRegister(l, p));
-//   if (_id) {
-//     let nick = l;
-//     if (nick.includes("@")) {
-//       nick = nick.substring(0, nick.indexOf("@"));
-//       if (nick.length > 8) {
-//         nick = nick.substring(0, 8);
-//       }
-//     }
-//     await dispatch(actionFullLogin(l, p));
-//     await dispatch(actionUserUpdate({ _id, nick }));
-//   }
-// };
 
 export const actionSetAvatar = (file) => ({
   type: "SET_AVATAR",
   file,
 });
-// async (dispatch, getState) => {
-//   let { _id } = await dispatch(actionUploadPhoto(file));
-//   let { id } = getState().auth.payload.sub;
-//   await dispatch(actionUserUpdate({ _id: id, avatar: { _id } }));
-//   await dispatch(actionAboutMe());
-// };
 
 export const actionSetNickname = ({ _id, nick }) => ({
   type: "SET_NICKNAME",
   _id,
   nick,
 });
-// async (dispatch) => {
-//   await dispatch(actionUserUpdate({ _id, nick }));
-//   await dispatch(actionAboutMe());
-// };
 
 export const actionSetEmail = ({ _id, login }) => ({
   type: "SET_EMAIL",
   _id,
   login,
 });
-// async (dispatch) => {
-//   await dispatch(actionUserUpdate({ _id, login }));
-//   await dispatch(actionAboutMe());
-// };
 
 export const actionSetNewPassword = (login, password, newPassword) => ({
   type: "SET_NEW_PASSWORD",
@@ -333,10 +322,6 @@ export const actionSetNewPassword = (login, password, newPassword) => ({
   password,
   newPassword,
 });
-// async (dispatch) => {
-//   await dispatch(actionChangePassword(login, password, newPassword));
-//   await dispatch(actionAboutMe());
-// };
 
 export const actionSearch = (text) => ({ type: "SEARCH", text });
 
@@ -345,29 +330,94 @@ export const actionSearchResult = (payload) => ({
   payload,
 });
 
-export const actionLoadAudio = (track, duration, playlist, playlistIndex) => ({
+export const actionLoadAudio = ({ track, playlist, indexInPlaylist }) => ({
   type: "LOAD_TRACK",
   track,
-  duration,
   playlist,
-  playlistIndex,
+  indexInPlaylist,
 });
 
-export const actionPlayAudio = (isPlaying) => ({
+export const actionFullLoadAudio = (track, playlist, indexInPlaylist) => ({
+  type: "FULL_LOAD_TRACK",
+  track,
+  playlist,
+  indexInPlaylist,
+});
+
+export const actionPlayAudio = ({ isPlaying }) => ({
   type: "PLAY_TRACK",
   isPlaying,
 });
 
-export const actionPauseAudio = (isPaused) => ({
+export const actionFullPlayAudio = (isPlaying) => ({
+  type: "FULL_PLAY_TRACK",
+  isPlaying,
+});
+
+export const actionPauseAudio = ({ isPaused }) => ({
   type: "PAUSE_TRACK",
   isPaused,
 });
 
-export const actionVolumeAudio = (volume) => ({
-  type: "VOLUME_TRACK",
+export const actionFullPauseAudio = (isPaused) => ({
+  type: "FULL_PAUSE_TRACK",
+  isPaused,
+});
+
+export const actionPrevTrack = ({ indexInPlaylist, track }) => ({
+  type: "PREV_TRACK",
+  indexInPlaylist,
+  track,
+});
+
+export const actionFullPrevTrack = (indexInPlaylist, track) => ({
+  type: "FULL_PREV_TRACK",
+  indexInPlaylist,
+  track,
+});
+
+export const actionNextTrack = ({ indexInPlaylist, track }) => ({
+  type: "NEXT_TRACK",
+  indexInPlaylist,
+  track,
+});
+
+export const actionFullNextTrack = (indexInPlaylist, track) => ({
+  type: "FULL_NEXT_TRACK",
+  indexInPlaylist,
+  track,
+});
+
+export const actionSetCurrentTimeTrack = ({ currentTime }) => ({
+  type: "SET_CURRENT_TIME_TRACK",
+  currentTime,
+});
+
+export const actionFullSetCurrentTimeTrack = (currentTime) => ({
+  type: "FULL_SET_CURRENT_TIME_TRACK",
+  currentTime,
+});
+
+export const actionSetVolume = ({ volume }) => ({
+  type: "SET_VOLUME",
+  volume,
+});
+
+export const actionFullSetVolume = (volume) => ({
+  type: "FULL_SET_VOLUME",
   volume,
 });
 
+export const actionSetDuration = ({ duration }) => ({
+  type: "SET_DURATION",
+  duration,
+});
+
+export const actionFullSetDuration = (duration) => ({
+  type: "FULL_SET_DURATION",
+  duration,
+});
+
 export const actionAllTracks = () => ({
   type: "FIND_ALL_TRACKS",
 });
@@ -389,10 +439,10 @@ export const actionPlaylistTracks = (_id) => ({
   _id,
 });
 
-export const actionTracksToPlaylist = (idTrack, idPlaylist) => ({
+export const actionTracksToPlaylist = (idPlaylist, array) => ({
   type: "LOAD_TRACKS_PLAYLIST",
-  idTrack,
   idPlaylist,
+  array,
 });
 
 export const actionUploadTracks = (file) => ({

+ 163 - 0
src/components/Audio.js

@@ -0,0 +1,163 @@
+import { takeEvery, put } from "redux-saga/effects";
+import { Button } from "react-bootstrap";
+import * as actions from "../actions";
+import { connect } from "react-redux";
+import { backURL } from "../helpers/index";
+import { Link } from "react-router-dom";
+import { useState } from "react";
+
+function* audioLoadWorker(track, playlist, indexInPlaylist) {
+  console.log("Load track");
+  yield put(actions.actionLoadAudio(track, playlist, indexInPlaylist));
+}
+
+export function* audioLoadWatcher() {
+  yield takeEvery("FULL_LOAD_TRACK", audioLoadWorker);
+}
+
+function* audioPlayWorker(isPlaying) {
+  console.log("Play track");
+  yield put(actions.actionPlayAudio(isPlaying));
+}
+
+export function* audioPlayWatcher() {
+  yield takeEvery("FULL_PLAY_TRACK", audioPlayWorker);
+}
+
+function* audioPauseWorker(isPaused) {
+  console.log("Pause track");
+  yield put(actions.actionPauseAudio(isPaused));
+}
+
+export function* audioPauseWatcher() {
+  yield takeEvery("FULL_PAUSE_TRACK", audioPauseWorker);
+}
+
+function* audioPrevTrackWorker(track, indexInPlaylist) {
+  console.log("Prev track");
+  yield put(actions.actionPrevTrack(track, indexInPlaylist));
+}
+
+export function* audioPrevTrackWatcher() {
+  yield takeEvery("FULL_PREV_TRACK", audioPrevTrackWorker);
+}
+
+function* audioNextTrackWorker(track, indexInPlaylist) {
+  console.log("Next track");
+  yield put(actions.actionNextTrack(track, indexInPlaylist));
+}
+
+export function* audioNextTrackWatcher() {
+  yield takeEvery("FULL_NEXT_TRACK", audioNextTrackWorker);
+}
+
+function* audioSetCurrentTimeWorker(currentTime) {
+  console.log("Set current time track");
+  yield put(actions.actionSetCurrentTimeTrack(currentTime));
+}
+
+export function* audioSetCurrentTimeWatcher() {
+  yield takeEvery("FULL_SET_CURRENT_TIME_TRACK", audioSetCurrentTimeWorker);
+}
+
+function* audioSetVolumeWorker(volume) {
+  console.log("Set volume");
+  yield put(actions.actionSetVolume(volume));
+}
+
+export function* audioSetVolumeWatcher() {
+  yield takeEvery("FULL_SET_VOLUME", audioSetVolumeWorker);
+}
+
+function* audioSetDurationWorker(duration) {
+  console.log("Set duration");
+  yield put(actions.actionSetDuration(duration));
+}
+
+export function* audioSetDurationWatcher() {
+  yield takeEvery("FULL_SET_DURATION", audioSetDurationWorker);
+}
+
+let audio = new Audio();
+
+const AudioTrack = ({
+  personal,
+  player,
+  track,
+  index,
+  playlist,
+  loadAudio,
+  playAudio,
+  pauseAudio,
+}) => {
+  const [play, setPlay] = useState(player?.isPlaying);
+
+  audio.src = `${backURL}/${track?.url}`;
+
+  function truncText(text) {
+    if (text && text.length > 40) {
+      return text.substring(0, 40) + "...";
+    } else return text;
+  }
+
+  let audioPlayPause = (track, playlist) => {
+    let indexInPlaylist = playlist.findIndex(
+      (trackPlaylist) => trackPlaylist?._id === track?._id
+    );
+
+    loadAudio(track, playlist, indexInPlaylist);
+    if (player?.isPaused) {
+      audio.play();
+      setPlay(true);
+      playAudio(true);
+    }
+    if (player?.isPlaying) {
+      audio.pause();
+      setPlay(false);
+      pauseAudio(true);
+    }
+  };
+
+  return (
+    <div className="d-flex mt-5">
+      <div className="customAudio p-2 bg-dark text-white">
+        <span className="ml-3 d-inline-block">
+          {index + 1} |{" "}
+          <span>
+            {track?.originalFileName
+              ? truncText(track.originalFileName)
+              : "Без названия"}
+          </span>
+        </span>
+      </div>
+      <Button onClick={() => audioPlayPause(track, playlist)}>
+        <i className={`fas ${play ? "fa-pause-circle" : "fa-play-circle"}`}></i>
+      </Button>
+      <a
+        className="btn btn-primary h-50 ml-1 my-auto"
+        href={`${backURL}/${track?.url}`}
+        role="button"
+      >
+        <i className="fas fa-download"></i>
+      </a>
+      <span>{track?.duration}</span>
+      {!personal ? (
+        <div className="ml-2">
+          <Link to={`/profile/${track?.owner?._id}`}>
+            {track?.owner
+              ? track?.owner?.nick
+                ? track.owner.nick
+                : track.owner.login
+              : "user"}
+          </Link>
+        </div>
+      ) : null}
+    </div>
+  );
+};
+
+export const CAudio = connect((state) => ({ player: state.player }), {
+  playAudio: actions.actionFullPlayAudio,
+  pauseAudio: actions.actionFullPauseAudio,
+  loadAudio: actions.actionFullLoadAudio,
+})(AudioTrack);

+ 125 - 36
src/components/AudioController.js

@@ -1,24 +1,77 @@
 import { Button } from "react-bootstrap";
 import { connect } from "react-redux";
 import { useState } from "react";
-import {
-  actionPlayAudio,
-  actionPauseAudio,
-  actionVolumeAudio,
-} from "./../actions/index";
+import * as actions from "../actions";
+import { backURL } from "../helpers";
+
+let audio = new Audio();
 
 const AudioController = ({
-  name,
-  currentTime,
-  duration,
-  volume,
+  promise,
+  player,
   playAudio,
   pauseAudio,
-  volumeAudio,
-  player,
+  prevAudio,
+  nextAudio,
+  setDuration,
+  setVolume,
+  setCurrentTime,
 }) => {
-  const [vol, setVol] = useState(volume);
-  const [reproduction, setReproduction] = useState(player?.isPlaying);
+  const [vol, setVol] = useState(player?.volume ? +player?.volume : 50);
+  const [dur, setDur] = useState(player?.duration ? player?.duration : 0);
+  const [pos, setPos] = useState(player?.currentTime ? player?.currentTime : 0);
+  const [indexInPlaylist, setIndexInPlaylist] = useState(
+    player?.indexInPlaylist
+  );
+
+  let audioInPlaylist = player?.playlist[player?.indexInPlaylist];
+  audio.src = `${backURL}/${audioInPlaylist?.url}`;
+
+  audio.onloadedmetadata = () => {
+    setDur(audio.duration);
+  };
+
+  audio.ondurationchange = () => {
+    // setDuration(dur);
+  };
+
+  audio.ontimeupdate = (event) => {
+    // console.log(audio.currentTime);
+  };
+
+  const checkIndexInPlaylist = (type) => {
+    if (type === "prev") {
+      if (player?.playlist[indexInPlaylist - 1]) {
+        setIndexInPlaylist(indexInPlaylist - 1);
+        return indexInPlaylist;
+      } else {
+        setIndexInPlaylist(player?.playlist?.length - 1);
+        return indexInPlaylist;
+      }
+    }
+    if (type === "next") {
+      if (player?.playlist[indexInPlaylist + 1]) {
+        setIndexInPlaylist(indexInPlaylist + 1);
+        return indexInPlaylist;
+      } else {
+        setIndexInPlaylist(0);
+        return indexInPlaylist;
+      }
+    }
+  };
+
+  const checkTrackInPlaylist = () => {};
+
+  const audioPlayPause = () => {
+    if (player?.isPlaying) {
+      // audio.pause();
+      pauseAudio(true);
+    }
+    if (player?.isPaused) {
+      // audio.play();
+      playAudio(true);
+    }
+  };
 
   function truncText(text) {
     if (text && text.includes(".mp3")) {
@@ -28,55 +81,91 @@ const AudioController = ({
 
   function convertTime(dur) {
     let minutes = Math.floor(dur / 60);
-    let seconds = parseInt((dur % 60) * 100) / 100;
-    return `${minutes}:${seconds}`;
+    let seconds = Math.floor(dur - minutes * 60);
+    let formatSeconds = seconds < 10 ? `0${seconds}` : seconds;
+    return Number.isNaN(dur) || dur === undefined
+      ? "00:00"
+      : `${minutes}:${formatSeconds}`;
   }
 
   return (
     <div className="AudioController">
-      <span className="SongName">{truncText(name)}</span>
+      <span className="SongName">
+        {truncText(player?.track?.originalFileName)}
+      </span>
       <div className="Buttons m-2">
-        <Button className="mr-2">
-          <i className="fas fa-step-backward"></i>
-        </Button>
         <Button
           className="mr-2"
           onClick={() => {
-            setReproduction(!reproduction);
-            reproduction ? pauseAudio(true) : playAudio(true);
+            checkIndexInPlaylist("prev");
+            console.log(indexInPlaylist, player?.playlist[indexInPlaylist]);
+            prevAudio(indexInPlaylist, player?.playlist[indexInPlaylist]);
           }}
         >
-          <i className={`fas fa-${reproduction ? "pause" : "caret-right"}`}></i>
+          <i className="fas fa-step-backward"></i>
         </Button>
-        <Button>
+        <Button className="mr-2" onClick={() => audioPlayPause()}>
+          <i
+            className={`fas fa-${player?.isPlaying ? "pause" : "caret-right"}`}
+          ></i>
+        </Button>
+        <Button
+          onClick={() => {
+            checkIndexInPlaylist("next");
+            console.log(indexInPlaylist, player?.playlist[indexInPlaylist]);
+            nextAudio(indexInPlaylist, player?.playlist[indexInPlaylist]);
+          }}
+        >
           <i className="fas fa-step-forward"></i>
         </Button>
         <div className="Duration">
-          <span>{convertTime(currentTime)}</span>
-          <input type="range" className="form-range mt-3 w-50" />
-          <span>{convertTime(duration)}</span>
+          <span>{convertTime(player?.currentTime)}</span>
+          <input
+            type="range"
+            className="form-range mt-3 w-50"
+            min={0}
+            max={
+              !player?.duration || Number.isNaN(player?.duration)
+                ? 0
+                : Math.trunc(player?.duration)
+            }
+            value={pos}
+            step={1}
+            onChange={(e) => {
+              setPos(e.target.value);
+              setCurrentTime(+pos);
+            }}
+          />
+          <span>{convertTime(dur)}</span>
         </div>
       </div>
       <div className="Volume m-2">
         <input
           type="range"
-          min="0"
-          step="1"
-          max="100"
           className="form-range"
+          min={0}
+          step={1}
+          max={100}
+          value={vol}
           onChange={(e) => {
             setVol(e.target.value);
-            volumeAudio(vol);
+            setVolume(+vol);
           }}
-          value={vol}
         />
       </div>
     </div>
   );
 };
 
-export const CAudioController = connect((state) => ({ player: state.player }), {
-  playAudio: actionPlayAudio,
-  pauseAudio: actionPauseAudio,
-  volumeAudio: actionVolumeAudio,
-})(AudioController);
+export const CAudioController = connect(
+  (state) => ({ promise: state.promise, player: state.player }),
+  {
+    playAudio: actions.actionFullPlayAudio,
+    pauseAudio: actions.actionFullPauseAudio,
+    nextAudio: actions.actionFullNextTrack,
+    prevAudio: actions.actionFullPrevTrack,
+    setVolume: actions.actionFullSetVolume,
+    setDuration: actions.actionFullSetDuration,
+    setCurrentTime: actions.actionFullSetCurrentTimeTrack,
+  }
+)(AudioController);

+ 16 - 3
src/components/Dropzone.js

@@ -2,12 +2,25 @@ import React, { useCallback } from "react";
 import { useDropzone } from "react-dropzone";
 import { connect } from "react-redux";
 import { actionSetAvatar, actionUploadTracks } from "../actions";
+import { history } from "./../App";
+
+const MyDropzone = ({ promise, onloadAvatar, onloadMusic }) => {
+  let idPlaylist = history.location.pathname.substring(
+    history.location.pathname.lastIndexOf("/") + 1
+  );
+  let indexPlaylist;
+  if (promise?.userPlaylists?.payload) {
+    indexPlaylist = promise?.userPlaylists?.payload.findIndex(
+      (playlist) => playlist?._id === idPlaylist
+    );
+  }
 
-const MyDropzone = ({ onloadAvatar, onloadMusic }) => {
   const onDrop = useCallback(
     (acceptedFiles) => {
       if (acceptedFiles[0].type.includes("audio")) {
-        onloadMusic(acceptedFiles[0]);
+        for (let i = 0; i < acceptedFiles.length; i++) {
+          onloadMusic(acceptedFiles[i]);
+        }
       } else {
         onloadAvatar(acceptedFiles[0]);
       }
@@ -31,7 +44,7 @@ const MyDropzone = ({ onloadAvatar, onloadMusic }) => {
   );
 };
 
-export const CMyDropzone = connect(null, {
+export const CMyDropzone = connect((state) => ({ promise: state.promise }), {
   onloadAvatar: actionSetAvatar,
   onloadMusic: actionUploadTracks,
 })(MyDropzone);

+ 3 - 3
src/components/Main.js

@@ -6,7 +6,7 @@ import { Page404 } from "../pages/Page404";
 import { CSearch } from "./../pages/Search";
 import { CLibrary } from "./../pages/Library";
 import { CProfile } from "./../pages/Profile";
-import { CMyPlaylistTracks } from "./Playlist";
+import { MyPlaylistTracks } from "./Playlist";
 
 const Content = ({ children }) => <div className="Content">{children}</div>;
 
@@ -27,10 +27,10 @@ export const Main = () => (
         <Route path="/signup" component={withRouter(CSignUpForm)} />
         <Route path="/search" component={withRouter(CSearch)} />
         <Route path="/library" component={withRouter(CLibrary)} />
-        <Route path="/profile" component={withRouter(CProfile)} />
+        <Route path="/profile/:_id" component={withRouter(CProfile)} />
         <Route
           path="/myplaylist/:_id"
-          component={withRouter(CMyPlaylistTracks)}
+          component={withRouter(MyPlaylistTracks)}
         />
         <Route path="" component={withRouter(Page404)} />
       </Switch>

+ 4 - 1
src/components/PlayerHeader.js

@@ -6,6 +6,9 @@ export const PlayerHeader = ({ personal }) => {
     window.onscroll = () => {
       setOffset(window.pageYOffset);
     };
+    return () => {
+      setOffset(0);
+    };
   }, []);
 
   return (
@@ -13,7 +16,7 @@ export const PlayerHeader = ({ personal }) => {
       <div className="container-fluid player-container">
         <span>#</span>
         <span>Название</span>
-        <span>{personal ? "Плейлист" : "Информация"}</span>
+        <span>{personal ? "" : "Владелец"}</span>
       </div>
     </nav>
   );

+ 54 - 37
src/components/Playlist.js

@@ -2,58 +2,77 @@ import { useState } from "react";
 import { connect } from "react-redux";
 import { Link } from "react-router-dom";
 import { CMyDropzone } from "./Dropzone";
+import { sortableContainer, sortableElement } from "react-sortable-hoc";
 import { actionCreateNewPlaylist } from "./../actions/index";
 import { PlayerHeader } from "./PlayerHeader";
 import { Loader } from "./Loader";
-import { CTrack } from "./Track";
 import { Button } from "react-bootstrap";
-import { backURL } from "./../helpers/index";
+import { CAudio } from "./Audio";
+import { history } from "./../App";
 
-const PlaylistTracks = ({ promise, tracks: { _id, url } = {} }) => (
-  <div>
-    {promise?.uploadTrack?.payload?.length !== 0 ? (
-      <div className="d-block mx-auto mt-2 container w-50">
-        <PlayerHeader personal />
-      </div>
-    ) : null}
-  </div>
-);
+const PlaylistTracks = ({ promise, tracks: { _id, url } = {} }) => {
+  let idPlaylist = history.location.pathname.substring(
+    history.location.pathname.lastIndexOf("/") + 1
+  );
+  let indexPlaylist;
+  if (promise?.userPlaylists?.payload) {
+    indexPlaylist = promise?.userPlaylists?.payload.findIndex(
+      (playlist) => playlist?._id === idPlaylist
+    );
+  }
 
-// <CTrack audio={} index={1} key={Math.random()} />
+  return (
+    <div>
+      {promise?.userPlaylists?.payload[indexPlaylist]?.tracks ? (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <PlayerHeader personal />
+        </div>
+      ) : null}
+      {promise?.loadTracksToPlaylist?.status === "PENDING" ||
+      promise?.playlistTracks?.status === "PENDING" ? (
+        <Loader />
+      ) : promise?.playlistTracks?.payload?.tracks ? (
+        promise?.playlistTracks?.payload?.tracks.map((track, index) => (
+          <div
+            className="d-block mx-auto mt-2 container w-50"
+            key={Math.random()}
+          >
+            <CAudio
+              track={track}
+              index={index}
+              playlist={promise?.playlistTracks?.payload?.tracks}
+              personal
+              key={track._id}
+            />
+          </div>
+        ))
+      ) : (
+        <h2 className="mt-5 text-center">
+          {promise?.myUser?.payload?.nick
+            ? promise?.myUser?.payload?.nick
+            : "user"}
+          , этот плейлист пуст.
+        </h2>
+      )}
+    </div>
+  );
+};
 
 const CPlaylistTracks = connect((state) => ({
   promise: state.promise,
   tracks: state.promise.uploadTrack?.payload || [],
 }))(PlaylistTracks);
 
-// {promise?.userTracks?.payload?.length !== 0 ? (
-//   <PlayerHeader personal />
-// ) : null}
-// {promise.userTracks.status === "PENDING" ? (
-//   <Loader />
-// ) : promise?.userTracks?.payload &&
-//   promise?.userTracks?.payload?.length !== 0 ? (
-//   promise.userTracks.payload.map((track, index) => (
-//     <CTrack audio={track} index={index} key={Math.random()} />
-//   ))
-// ) : (
-//   <h2 className="mt-5 text-center">
-//     {promise?.user?.payload?.nick ? promise?.user?.payload?.nick : "user"},
-//     ваша библиотека пуста.
-//   </h2>
-// )}
-const MyPlaylistTracks = ({ promise }) => (
+export const MyPlaylistTracks = () => (
   <>
+    <h3 className="text-center">
+      Перетащите аудио файл(-ы) для загрузки в этот плейлист.
+    </h3>
     <CMyDropzone />
     <CPlaylistTracks />
   </>
 );
 
-export const CMyPlaylistTracks = connect(
-  (state) => ({ promise: state.promise }),
-  null
-)(MyPlaylistTracks);
-
 const PlaylistEditor = ({
   entity = { array: [] },
   onSave,
@@ -113,9 +132,7 @@ const MyPlaylists = ({ promise, onPlaylistCreate }) => {
           type="submit"
           className="btn btn-primary"
           disabled={playlist.length > 1 && playlist.length < 11 ? false : true}
-          onClick={() => {
-            onPlaylistCreate(playlist);
-          }}
+          onClick={() => onPlaylistCreate(playlist)}
         >
           Создать
         </button>

+ 338 - 0
src/components/Profile/MyProfile.js

@@ -0,0 +1,338 @@
+import { AuthCheck } from "../AuthCheck";
+import { Spoiler } from "../Spoiler";
+import {
+  backURL,
+  validateEmail,
+  validatePassword,
+  validateNickname,
+} from "../../helpers/index";
+import {
+  actionSetNickname,
+  actionSetEmail,
+  actionSetNewPassword,
+} from "../../actions/index";
+import { CMyDropzone } from "../Dropzone";
+import { Form, Alert, Row, Col, Button } from "react-bootstrap";
+import { useState } from "react";
+import { connect } from "react-redux";
+import { CUserInfo } from "./UserInfo";
+import { Loader } from "../Loader";
+
+const MyProfile = ({
+  id,
+  auth,
+  promise,
+  emailUpdate,
+  nickUpdate,
+  changePassword,
+}) => {
+  const [login, setLogin] = useState("");
+  const [nick, setNickname] = useState("");
+  const [password, setPassword] = useState("");
+  const [newPassword, setNewPassword] = useState("");
+  const [passwordShown, setPasswordShown] = useState(false);
+
+  return (
+    <div className="ProfilePage">
+      {auth.token ? (
+        <>
+          
+          {id === auth?.payload?.sub?.id ? (
+            promise?.myUser?.status === "RESOLVED" ? (
+              <CUserInfo id={id} />
+            ) : (
+              <Loader />
+            )
+          ) : promise?.anotherUser?.status === "RESOLVED" ? (
+            <CUserInfo id={id} />
+          ) : (
+            <Loader />
+          )}
+          {id === auth?.payload?.sub?.id ? (
+            <div className="d-block mx-auto mt-2 container w-50">
+              <h1>
+                Редактирование профиля:{" "}
+                {promise?.myUser?.payload?.nick
+                  ? promise?.myUser?.payload?.nick
+                  : "user"}
+              </h1>
+              <Spoiler
+                children={
+                  <>
+                    <br />
+                    <form
+                      action="/upload"
+                      method="post"
+                      encType="multipart/form-data"
+                      id="form"
+                    >
+                      <img
+                        className="avatarProfile"
+                        src={
+                          promise?.myUser?.payload?.avatar
+                            ? `${backURL}/${promise.myUser.payload.avatar.url}`
+                            : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
+                        }
+                        alt="Avatar"
+                      />
+                      <CMyDropzone />
+                    </form>
+                  </>
+                }
+                header={<h3>Изменить аватар</h3>}
+              />
+              <Spoiler
+                children={
+                  <>
+                    <br />
+                    <Form>
+                      {promise?.myUser?.payload?.nick === nick ? (
+                        <Alert>
+                          Никнейм не должен повторяться с предыдущим.
+                        </Alert>
+                      ) : null}
+                      {validateNickname(nick) ? null : (
+                        <Alert>
+                          Никнейм может состоять только из строчных букв и цифр,
+                          символы - и _, а так же иметь длину от 3 до 8
+                          символов.
+                        </Alert>
+                      )}
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalEmail"
+                      >
+                        <Form.Label column sm={2}>
+                          Ваш никнейм:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type="text"
+                            placeholder="Ваш текущий никнейм"
+                            value={
+                              promise?.myUser?.payload?.nick
+                                ? promise?.myUser?.payload?.nick
+                                : "Никнейм не установлен"
+                            }
+                            disabled
+                          />
+                        </Col>
+                      </Form.Group>
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalEmail"
+                      >
+                        <Form.Label column sm={2}>
+                          Новый никнейм:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type="text"
+                            placeholder="Введите ваш новый никнейм"
+                            value={nick}
+                            max="8"
+                            onChange={(e) => setNickname(e.target.value)}
+                          />
+                        </Col>
+                      </Form.Group>
+                      <Form.Group as={Row} className="mb-3">
+                        <Col sm={{ span: 10, offset: 2 }} className="my-3">
+                          <Button
+                            variant="success"
+                            disabled={
+                              promise?.myUser?.payload?.nick !== nick &&
+                              validateNickname(nick)
+                                ? false
+                                : true
+                            }
+                            onClick={() =>
+                              nickUpdate({
+                                _id: promise?.myUser?.payload?._id,
+                                nick,
+                              })
+                            }
+                          >
+                            Сохранить
+                          </Button>
+                        </Col>
+                      </Form.Group>
+                    </Form>
+                  </>
+                }
+                header={<h3>Изменить никнейм</h3>}
+              />
+              <Spoiler
+                children={
+                  <>
+                    <br />
+                    <Form>
+                      {promise?.myUser?.payload?.login === login ? (
+                        <Alert>Email не должен повторяться с предыдущим.</Alert>
+                      ) : null}
+                      {validateEmail(login) ? null : (
+                        <Alert>
+                          Email должен быть в формате: email@gmail.com.
+                        </Alert>
+                      )}
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalEmail"
+                      >
+                        <Form.Label column sm={2}>
+                          Ваша почта:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type="text"
+                            placeholder="Ваша текущая почта"
+                            value={promise?.myUser?.payload?.login}
+                            disabled
+                          />
+                        </Col>
+                      </Form.Group>
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalEmail"
+                      >
+                        <Form.Label column sm={2}>
+                          Новая почта:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type="text"
+                            placeholder="Введите вашу новую почту"
+                            value={login}
+                            onChange={(e) => setLogin(e.target.value)}
+                          />
+                        </Col>
+                      </Form.Group>
+                      <Form.Group as={Row} className="mb-3">
+                        <Col sm={{ span: 10, offset: 2 }} className="my-3">
+                          <Button
+                            variant="success"
+                            disabled={
+                              validateEmail(login) &&
+                              promise?.myUser?.payload?.login !== login
+                                ? false
+                                : true
+                            }
+                            onClick={() =>
+                              emailUpdate({
+                                _id: promise?.myUser?.payload?._id,
+                                login,
+                              })
+                            }
+                          >
+                            Сохранить
+                          </Button>
+                        </Col>
+                      </Form.Group>
+                    </Form>
+                  </>
+                }
+                header={<h3>Изменить почту</h3>}
+              />
+              <Spoiler
+                children={
+                  <>
+                    <br />
+                    <Form>
+                      {password.length !== 0 ? null : (
+                        <Alert>
+                          Пожалуйста, введите свой текущий пароль в первое поле
+                          для изменения пароля.
+                        </Alert>
+                      )}
+                      {validatePassword(newPassword) ? null : (
+                        <Alert>
+                          Новый пароль должен быть от 6 символов, иметь хотя бы
+                          одну цифру и заглавную букву.
+                        </Alert>
+                      )}
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalPassword"
+                      >
+                        <Form.Label column sm={2}>
+                          Пароль:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type={passwordShown ? "text" : "password"}
+                            placeholder="Введите ваш текущий пароль"
+                            onChange={(e) => setPassword(e.target.value)}
+                          />
+                          <Button
+                            className="mt-2"
+                            variant="secondary"
+                            onClick={() => setPasswordShown(!passwordShown)}
+                          >
+                            {`${passwordShown ? "Hide" : "Show"} passwords`}
+                          </Button>
+                        </Col>
+                      </Form.Group>
+                      <Form.Group
+                        as={Row}
+                        className="m-2"
+                        controlId="formHorizontalPassword"
+                      >
+                        <Form.Label column sm={2}>
+                          Новый пароль:
+                        </Form.Label>
+                        <Col sm={10}>
+                          <Form.Control
+                            type={passwordShown ? "text" : "password"}
+                            placeholder="Введите ваш новый пароль"
+                            onChange={(e) => setNewPassword(e.target.value)}
+                          />
+                        </Col>
+                      </Form.Group>
+                      <Form.Group as={Row} className="mb-3">
+                        <Col sm={{ span: 10, offset: 2 }} className="my-3">
+                          <Button
+                            variant="success"
+                            disabled={
+                              validatePassword(newPassword) ? false : true
+                            }
+                            onClick={() =>
+                              changePassword(
+                                promise?.myUser?.payload?.login,
+                                password,
+                                newPassword
+                              )
+                            }
+                          >
+                            Сохранить
+                          </Button>
+                        </Col>
+                      </Form.Group>
+                    </Form>
+                  </>
+                }
+                header={<h3>Изменить пароль</h3>}
+              />
+            </div>
+          ) : null}
+        </>
+      ) : (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <AuthCheck header="Ваш профиль" />
+        </div>
+      )}
+    </div>
+  );
+};
+
+export const CMyProfile = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    emailUpdate: actionSetEmail,
+    nickUpdate: actionSetNickname,
+    changePassword: actionSetNewPassword,
+  }
+)(MyProfile);

+ 88 - 0
src/components/Profile/UserInfo.js

@@ -0,0 +1,88 @@
+import { connect } from "react-redux";
+import { backURL } from "../../helpers/index";
+import { Page404 } from "../../pages/Page404";
+// 61e9c85ec2305e2f502acd77s
+
+const InfoRender = ({ avatar, nick, date }) => {
+  let dmy = [],
+    dateReg = new Date(+date);
+  dmy = [
+    ("0" + dateReg.getDate()).slice(-2),
+    ("0" + (dateReg.getMonth() + 1)).slice(-2),
+    dateReg.getFullYear(),
+  ];
+  return (
+    <>
+    {date ? <div className="text-center">
+    <h1 className="text-center">Информация о профиле пользователя:</h1>
+      <div>
+        <img
+          style={{ width: "6rem" }}
+          src={
+            avatar
+              ? `${backURL}/${avatar}`
+              : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
+          }
+          className="card-img-top"
+          alt="ProfileImage"
+        />
+      </div>
+      <div>Никнейм: {nick}</div>
+      <div>Дата регистрации: {dmy.join(".")}</div>
+    </div> : <Page404 />}
+    </>
+  );
+};
+
+const UserInfo = ({ id, auth, promise }) => {
+  return (
+    <>
+      {id === auth?.payload?.sub?.id ? (
+        <InfoRender
+          avatar={promise?.myUser?.payload?.avatar?.url}
+          nick={promise?.myUser?.payload?.nick}
+          date={promise?.myUser?.payload?.createdAt}
+        />
+      ) : (
+        <InfoRender
+          avatar={promise?.anotherUser?.payload?.avatar?.url}
+          nick={
+            promise?.anotherUser?.payload?.nick
+              ? promise?.anotherUser?.payload?.nick
+              : promise?.anotherUser?.payload?.login
+          }
+          date={promise?.anotherUser?.payload?.createdAt}
+        />
+      )}
+    </>
+  );
+};
+
+// {promise?.myTracks?.status === "RESOLVED" ||
+//       promise?.anotherUserTracks?.status === "RESOLVED" ? (
+//         id === auth?.payload?.sub?.id ? (
+//           promise?.myTracks?.payload ? (
+//             <InfoRender
+//               avatar={promise?.myUser?.payload?.avatar?.url}
+//               nick={promise?.myUser?.payload?.nick}
+//               tracksCount={promise?.myTracks?.payload?.length}
+//             />
+//           ) : null
+//         ) : promise?.anotherUserTracks?.payload ? (
+//           <InfoRender
+//             avatar={promise?.anotherUser?.payload?.avatar?.url}
+//             nick={promise?.anotherUser?.payload?.nick}
+//             tracksCount={promise?.anotherUserTracks?.payload?.length}
+//           />
+//         ) : null
+//       ) : (
+//         <Loader />
+//       )}
+
+export const CUserInfo = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  null
+)(UserInfo);
+
+// findMyTracks: actionFindMyTracks,
+// findAnotherUserTracks: actionFindAnotherUserTracks,

+ 11 - 7
src/components/SearchResult.js

@@ -1,13 +1,17 @@
 import { connect } from "react-redux";
-import { CTrack } from "./Track";
+import { CTracks } from "./Tracks";
 
-const SearchResult = ({ search }) => {
-  return (search?.searchResult?.payload?.payload || []).map((track, index) => (
-    <CTrack audio={track} index={index} key={Math.random()} />
-  ));
-};
+const SearchResult = ({ promise, search }) =>
+  search?.searchResult?.payload?.payload ? (
+    <CTracks tracks={search?.searchResult?.payload?.payload} />
+  ) : (
+    <h2 className="mt-5 text-center">
+      {promise?.user?.payload?.nick ? promise?.user?.payload?.nick : "user"}, по
+      вашему запросу ничего не было найдено.
+    </h2>
+  );
 
 export const CSearchResult = connect(
-  (state) => ({ search: state.search || [] }),
+  (state) => ({ promise: state.promise, search: state.search || [] }),
   null
 )(SearchResult);

+ 0 - 93
src/components/Track.js

@@ -1,93 +0,0 @@
-import { Button } from "react-bootstrap";
-import { connect } from "react-redux";
-import { backURL } from "../helpers/index";
-import { useState } from "react";
-import { CAudioController } from "./AudioController";
-import { Link } from "react-router-dom";
-import {
-  actionPauseAudio,
-  actionPlayAudio,
-  actionVolumeAudio,
-  actionLoadAudio,
-} from "./../actions/index";
-
-const Track = ({
-  audio,
-  index,
-  loadAudio,
-  playAudio,
-  pauseAudio,
-  volumeAudio,
-  player,
-  promise,
-}) => {
-  const [reproduction, setReproduction] = useState(false);
-  let track = audio?.url ? `${backURL}/${audio.url}` : undefined;
-  let audioTrack = new Audio(track);
-  let duration, currentTime, volume;
-  audioTrack.addEventListener("loadeddata", () => {
-    duration = audioTrack.duration;
-    currentTime = audioTrack.currentTime;
-    volume = audioTrack.volume;
-  });
-
-  if (reproduction) {
-    audioTrack.play();
-  } else {
-    audioTrack.pause();
-  }
-
-  function truncText(text) {
-    if (text && text.length > 40) {
-      return text.substring(0, 40) + "...";
-    } else return text;
-  }
-
-  return (
-    <>
-      <div className="d-flex mt-5">
-        <div className="customAudio p-2 bg-dark text-white">
-          <span className="ml-3 d-inline-block">
-            {index + 1} |{" "}
-            <span>
-              {audio?.originalFileName
-                ? truncText(audio.originalFileName)
-                : "Без названия"}
-            </span>
-          </span>
-        </div>
-        <Button
-          onClick={() => {
-            setReproduction(!reproduction);
-            reproduction ? pauseAudio(true) : playAudio(true);
-          }}
-        >
-          <i
-            className={`fas ${
-              reproduction ? "fa-pause-circle" : "fa-play-circle"
-            }`}
-          ></i>
-        </Button>
-        <Button>
-          <i className="fas fa-download"></i>
-        </Button>
-        <div className="ml-5">
-          Загрузил:{" "}
-          <Link to={`/profile/${audio?.owner?._id}`}>
-            {audio?.owner?.login}
-          </Link>
-        </div>
-      </div>
-    </>
-  );
-};
-
-export const CTrack = connect(
-  (state) => ({ promise: state.promise, player: state.player }),
-  {
-    loadAudio: actionLoadAudio,
-    playAudio: actionPlayAudio,
-    pauseAudio: actionPauseAudio,
-    volumeAudio: actionVolumeAudio,
-  }
-)(Track);

+ 17 - 0
src/components/Tracks.js

@@ -0,0 +1,17 @@
+import { connect } from "react-redux";
+import { CAudio } from "./Audio";
+
+const Tracks = ({ tracks, player }) => {
+  return (
+    <>
+      {tracks.map((track, index) => (
+        <CAudio track={track} index={index} playlist={tracks} key={track._id} />
+      ))}
+    </>
+  );
+};
+
+export const CTracks = connect(
+  (state) => ({ player: state.player }),
+  null
+)(Tracks);

+ 12 - 8
src/pages/Auth.js

@@ -2,17 +2,22 @@ import { actionAuthLogout } from "../actions/index";
 import { NavDropdown } from "react-bootstrap";
 import { Link } from "react-router-dom";
 import { connect } from "react-redux";
-import { history } from "../App";
+import { history, store } from "../App";
 import { backURL } from "../helpers";
 
 const Auth = ({ auth, promise, actionLogOut }) => {
   if (
-    auth.token &&
+    auth?.token &&
     (history.location.pathname === "/login" ||
       history.location.pathname === "/signup")
   ) {
     history.push("/");
   }
+  let id;
+  if (auth?.token) {
+    id = store.getState()?.auth?.payload?.sub?.id;
+  }
+
   return (
     <>
       {auth.payload ? (
@@ -23,25 +28,24 @@ const Auth = ({ auth, promise, actionLogOut }) => {
               <img
                 className="thumbnail-image avatarHeader"
                 src={
-                  promise?.user?.payload?.avatar
-                    ? `${backURL}/${promise.user.payload.avatar.url}`
+                  promise?.myUser?.payload?.avatar
+                    ? `${backURL}/${promise.myUser.payload.avatar.url}`
                     : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
                 }
                 alt="Avatar"
               />
-              {promise?.user?.payload?.nick}
+              {promise?.myUser?.payload?.nick}
             </div>
           }
           menuVariant="dark"
         >
           <NavDropdown.Item
             componentclass={Link}
-            href="/profile"
-            to={`/profile`}
+            href={`/profile/${id}`}
+            to={`/profile/${id}`}
           >
             Профиль
           </NavDropdown.Item>
-
           <NavDropdown.Divider />
           <NavDropdown.Item as="button" onClick={() => actionLogOut()}>
             Выйти

+ 9 - 304
src/pages/Profile.js

@@ -1,314 +1,19 @@
 import { connect } from "react-redux";
 import { history } from "./../App";
-import { Button, Form, Row, Col, Alert } from "react-bootstrap";
-import { AuthCheck } from "./../components/AuthCheck";
-import { useState } from "react";
+import { CMyProfile } from "./../components/Profile/MyProfile";
+import { actionAboutMe, actionAboutAnotherUser } from "./../actions/index";
 
-import {
-  backURL,
-  validateEmail,
-  validatePassword,
-  validateNickname,
-} from "./../helpers/index";
-import {
-  actionSetNickname,
-  actionSetEmail,
-  actionSetNewPassword,
-} from "./../actions/index";
-import { Loader } from "../components/Loader";
-import { CMyDropzone } from "../components/Dropzone";
-import { Spoiler } from "../components/Spoiler";
-
-const Profile = ({
-  auth,
-  promise,
-  nickUpdate,
-  emailUpdate,
-  changePassword,
-}) => {
-  const [login, setLogin] = useState("");
-  const [nick, setNickname] = useState("");
-  const [password, setPassword] = useState("");
-  const [newPassword, setNewPassword] = useState("");
-  const [passwordShown, setPasswordShown] = useState(false);
-
-  return promise?.user?.status === "PENDING" ? (
-    <Loader />
-  ) : (
-    <div className="ProfilePage">
-      {auth.token && history.location.pathname === "/profile" ? (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <h1>
-            Ваш профиль,{" "}
-            {promise?.user?.payload?.nick
-              ? promise?.user?.payload?.nick
-              : "user"}
-          </h1>
-          <Spoiler
-            children={
-              <>
-                <br />
-                <form
-                  action="/upload"
-                  method="post"
-                  encType="multipart/form-data"
-                  id="form"
-                >
-                  <img
-                    className="avatarProfile"
-                    src={
-                      promise?.user?.payload?.avatar
-                        ? `${backURL}/${promise.user.payload.avatar.url}`
-                        : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
-                    }
-                    alt="Avatar"
-                  />
-                  <CMyDropzone />
-                </form>
-              </>
-            }
-            header={<h3>Изменить аватар</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  {promise?.user?.payload?.nick === nick ? (
-                    <Alert>Никнейм не должен повторяться с предыдущим.</Alert>
-                  ) : null}
-                  {validateNickname(nick) ? null : (
-                    <Alert>
-                      Никнейм может состоять только из строчных букв и цифр,
-                      символы - и _, а так же иметь длину от 3 до 8 символов.
-                    </Alert>
-                  )}
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalEmail"
-                  >
-                    <Form.Label column sm={2}>
-                      Ваш никнейм:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type="text"
-                        placeholder="Ваш текущий никнейм"
-                        value={
-                          promise?.user?.payload?.nick
-                            ? promise?.user?.payload?.nick
-                            : "Никнейм не установлен"
-                        }
-                        disabled
-                      />
-                    </Col>
-                  </Form.Group>
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalEmail"
-                  >
-                    <Form.Label column sm={2}>
-                      Новый никнейм:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type="text"
-                        placeholder="Введите ваш новый никнейм"
-                        value={nick}
-                        max="8"
-                        onChange={(e) => setNickname(e.target.value)}
-                      />
-                    </Col>
-                  </Form.Group>
-                  <Form.Group as={Row} className="mb-3">
-                    <Col sm={{ span: 10, offset: 2 }} className="my-3">
-                      <Button
-                        variant="success"
-                        disabled={
-                          promise?.user?.payload?.nick !== nick &&
-                          validateNickname(nick)
-                            ? false
-                            : true
-                        }
-                        onClick={() =>
-                          nickUpdate({ _id: promise?.user?.payload?._id, nick })
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить никнейм</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  {promise?.user?.payload?.login === login ? (
-                    <Alert>Email не должен повторяться с предыдущим.</Alert>
-                  ) : null}
-                  {validateEmail(login) ? null : (
-                    <Alert>Email должен быть в формате: email@gmail.com.</Alert>
-                  )}
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalEmail"
-                  >
-                    <Form.Label column sm={2}>
-                      Ваша почта:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type="text"
-                        placeholder="Ваша текущая почта"
-                        value={promise?.user?.payload?.login}
-                        disabled
-                      />
-                    </Col>
-                  </Form.Group>
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalEmail"
-                  >
-                    <Form.Label column sm={2}>
-                      Новая почта:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type="text"
-                        placeholder="Введите вашу новую почту"
-                        value={login}
-                        onChange={(e) => setLogin(e.target.value)}
-                      />
-                    </Col>
-                  </Form.Group>
-                  <Form.Group as={Row} className="mb-3">
-                    <Col sm={{ span: 10, offset: 2 }} className="my-3">
-                      <Button
-                        variant="success"
-                        disabled={
-                          validateEmail(login) &&
-                          promise?.user?.payload?.login !== login
-                            ? false
-                            : true
-                        }
-                        onClick={() =>
-                          emailUpdate({
-                            _id: promise?.user?.payload?._id,
-                            login,
-                          })
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить почту</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  {password.length !== 0 ? null : (
-                    <Alert>
-                      Пожалуйста, введите свой текущий пароль в первое поле для
-                      изменения пароля.
-                    </Alert>
-                  )}
-                  {validatePassword(newPassword) ? null : (
-                    <Alert>
-                      Новый пароль должен быть от 6 символов, иметь хотя бы одну
-                      цифру и заглавную букву.
-                    </Alert>
-                  )}
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalPassword"
-                  >
-                    <Form.Label column sm={2}>
-                      Пароль:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type={passwordShown ? "text" : "password"}
-                        placeholder="Введите ваш текущий пароль"
-                        onChange={(e) => setPassword(e.target.value)}
-                      />
-                      <Button
-                        className="mt-2"
-                        variant="secondary"
-                        onClick={() => setPasswordShown(!passwordShown)}
-                      >
-                        {`${passwordShown ? "Hide" : "Show"} passwords`}
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalPassword"
-                  >
-                    <Form.Label column sm={2}>
-                      Новый пароль:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type={passwordShown ? "text" : "password"}
-                        placeholder="Введите ваш новый пароль"
-                        onChange={(e) => setNewPassword(e.target.value)}
-                      />
-                    </Col>
-                  </Form.Group>
-                  <Form.Group as={Row} className="mb-3">
-                    <Col sm={{ span: 10, offset: 2 }} className="my-3">
-                      <Button
-                        variant="success"
-                        disabled={validatePassword(newPassword) ? false : true}
-                        onClick={() =>
-                          changePassword(
-                            promise?.user?.payload?.login,
-                            password,
-                            newPassword
-                          )
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить пароль</h3>}
-          />
-        </div>
-      ) : (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <AuthCheck header="Ваш профиль" />
-        </div>
-      )}
-    </div>
+const Profile = ({ promise, auth, aboutMe, aboutAnotherUser }) => {
+  let currentUserId = history.location.pathname.substring(
+    history.location.pathname.lastIndexOf("/") + 1
   );
+  return <CMyProfile id={currentUserId} />;
 };
 
 export const CProfile = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
+  (state) => ({ promise: state.promise, auth: state.auth }),
   {
-    emailUpdate: actionSetEmail,
-    nickUpdate: actionSetNickname,
-    changePassword: actionSetNewPassword,
+    aboutMe: actionAboutMe,
+    aboutAnotherUser: actionAboutAnotherUser,
   }
 )(Profile);

+ 4 - 7
src/pages/Search.js

@@ -2,7 +2,7 @@ import { connect } from "react-redux";
 import { CSearchResult } from "../components/SearchResult";
 import { AuthCheck } from "./../components/AuthCheck";
 import { history } from "./../App";
-import { CTrack } from "../components/Track";
+import { CTracks } from "../components/Tracks";
 import { PlayerHeader } from "./../components/PlayerHeader";
 import { Loader } from "./../components/Loader";
 import { useState } from "react";
@@ -40,13 +40,10 @@ const Search = ({ search, auth, promise }) => {
           {promise?.tracks?.payload?.length !== 0 ? <PlayerHeader /> : null}
           {search?.searchResult?.payload?.payload ? (
             <CSearchResult />
-          ) : promise.tracks.status === "PENDING" ? (
+          ) : promise?.tracks?.status === "PENDING" ? (
             <Loader />
-          ) : promise?.tracks?.payload &&
-            promise?.tracks?.payload?.length !== 0 ? (
-            promise.tracks.payload.map((track, index) => (
-              <CTrack audio={track} index={index} key={Math.random()} />
-            ))
+          ) : promise?.tracks?.payload?.length !== 0 ? (
+            <CTracks tracks={promise.tracks.payload} />
           ) : (
             <h2 className="mt-5 text-center">
               {promise?.user?.payload?.nick

+ 42 - 15
src/reducers/index.js

@@ -49,26 +49,34 @@ export const localStoredReducer =
     }
   };
 
-// track: {_id, url, originalFileName}
-// playlist: {_id, name, tracks: [{_id}, {_id}, ...tracks]}
-
-export const searchReducer = (state={}, {type, ...params}) => {
-  if (type === 'SEARCH_RESULT'){
-      return {searchResult: {...params}}
+export const searchReducer = (state = {}, { type, ...params }) => {
+  if (type === "SEARCH_RESULT") {
+    return { searchResult: { ...params } };
   }
-  return state//, в таком случае вызываются все редьюсеры, но далеко не всегда action.type будет относится к этому редьюсеру. Тогда редьюсер должен вернуть state как есть. 
-}
+  return state;
+};
+
+let initState = {
+  isPlaying: false,
+  isPaused: true,
+  duration: 0,
+  track: null, // {_id, url, id3.........}
+  playlist: null, // {_id, name, tracks: [{-id}, {_id} ....]}
+  indexInPlaylist: null,
+  currentTime: 0,
+  volume: 0.5,
+};
 
 export const playerReducer = (
-  state = {},
+  state = initState,
   {
     type,
-    track,
     isPlaying,
     isPaused,
     duration,
+    track,
     playlist,
-    playlistIndex,
+    indexInPlaylist,
     currentTime,
     volume,
   }
@@ -77,9 +85,8 @@ export const playerReducer = (
     return {
       ...state,
       track,
-      duration,
       playlist,
-      playlistIndex,
+      indexInPlaylist,
     };
   }
   if (type === "PLAY_TRACK") {
@@ -96,13 +103,33 @@ export const playerReducer = (
       isPlaying: !isPaused,
     };
   }
-  if (type === "VOLUME_TRACK") {
+  if (type === "PREV_TRACK") {
+    return {
+      ...state,
+      indexInPlaylist,
+      track,
+    };
+  }
+  if (type === "NEXT_TRACK") {
+    return {
+      ...state,
+      indexInPlaylist,
+      track,
+    };
+  }
+  if (type === "SET_VOLUME") {
     return {
       ...state,
       volume,
     };
   }
-  if (type === "TIME_TRACK") {
+  if (type === "SET_DURATION") {
+    return {
+      ...state,
+      duration,
+    };
+  }
+  if (type === "SET_CURRENT_TIME_TRACK") {
     return {
       ...state,
       currentTime,