Просмотр исходного кода

changed filter control, deleted unused files, fixed volume control icons logic

Alyona Brytvina 2 лет назад
Родитель
Сommit
333a869e94

+ 31 - 33
src/api/auth.js

@@ -1,12 +1,11 @@
 import { getGql } from '../utils/getGql';
-import { BACKEND_URL } from '../constants';
 
 export const login = (payload) => {
   const {login, password} = payload;
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
-      query log ($login:String!, $password:String!) {
-       login(login:$login, password:$password)
+
+  return getGql(`
+       query log ($login:String!, $password:String!) {
+            login(login:$login, password:$password)
        }
      `, {
     login,
@@ -16,14 +15,17 @@ export const login = (payload) => {
 
 export const registration = (payload) => {
   const {login, password} = payload;
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(
-    `mutation reg($login: String!, $password: String!){
-         createUser(login:$login,
-              password: $password){
-         _id login
+
+  return getGql(
+    `
+      mutation reg($login: String!, $password: String!){
+         createUser(
+             login:$login,
+             password: $password
+         ){
+             _id login
          }
-    }
+      }
     `,
     {
       login,
@@ -32,31 +34,27 @@ export const registration = (payload) => {
   );
 };
 
-export const findUserById = (_id) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
-      query findUserById($id: String){
-            UserFindOne(query: $id) {
-                _id, login, nick, createdAt, avatar {
-            _id, url
-            }
-      }
-  }
-     `, {id: JSON.stringify([{_id}])});
-};
+export const findUserById = (_id) => getGql(`
+    query findUserById($id: String){
+       UserFindOne(query: $id) {
+           _id, login, nick, createdAt, avatar {
+               _id, url
+           }
+       }
+    }
+     `, {
+  id: JSON.stringify([{_id}]),
+});
 
-export const setNick = ({id, nick}) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  console.log(id, nick);
-  return gql(`
+export const setNick = ({id, nick}) => getGql(`
       mutation setNick{
           UserUpsert(user: {
              _id: "${id}", nick: "${nick}",
-             }){
-          _id, login, nick, createdAt, avatar {
-            _id, url
-            }
+             }
+          ){
+             _id, login, nick, createdAt, avatar {
+                  _id, url
+             }
           }
       }
   `);
-};

+ 40 - 59
src/api/playlists.js

@@ -1,25 +1,19 @@
 import { getGql } from '../utils/getGql';
 import { BACKEND_URL } from '../constants';
 
-export const getPlaylists = () => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`query nonEmptyPlaylists($query: String){
+export const getPlaylists = () => getGql(`query nonEmptyPlaylists($query: String){
   PlaylistFind(query: $query) {
    _id name 
   }
 }
     `, {
-    query: JSON.stringify([{
-      name: {$exists: true, $ne: ''},
-      tracks: {$exists: true, $ne: []},
-    }]),
-  });
-};
+  query: JSON.stringify([{
+    name: {$exists: true, $ne: ''},
+    tracks: {$exists: true, $ne: []},
+  }]),
+});
 
-export const getSelectedPlaylist = (_id) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  console.log(_id);
-  return gql(`query FindOnePlaylist($playlist:String!) {
+export const getSelectedPlaylist = (_id) => getGql(`query FindOnePlaylist($playlist:String!) {
          PlaylistFindOne(query:$playlist){
                _id name description tracks{
                      _id url originalFileName
@@ -27,63 +21,49 @@ export const getSelectedPlaylist = (_id) => {
          }
     } 
     `, {playlist: JSON.stringify([{_id}])})
-    .then(data => data.tracks.map(track => ({
-      ...track,
-      url: `${BACKEND_URL}/${track.url}`,
-    })));
-};
+  .then(data => data.tracks.map(track => ({
+    ...track,
+    url: `${BACKEND_URL}/${track.url}`,
+  })));
 
-export const getPlaylistsWithPage = (page = 1) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
+export const getPlaylistsWithPage = (page = 1) => getGql(`
       query skipPlaylist($query: String){
           PlaylistFind(query:$query){
           _id name 
            }
       }
   `, {
-    query: JSON.stringify([{
-      name: {$exists: true, $ne: ''},
-      tracks: {$exists: true, $ne: []},
-    },
-    {
-      limit: [20],
-      skip: [(page - 1) * 20],
-    }])
-    ,
-  });
-};
+  query: JSON.stringify([{
+    name: {$exists: true, $ne: ''},
+    tracks: {$exists: true, $ne: []},
+  },
+  {
+    limit: [20],
+    skip: [(page - 1) * 20],
+  }])
+  ,
+});
 
-export const getPlaylistsCount = () => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
+export const getPlaylistsCount = () => getGql(`
       query getCount($query: String){
        PlaylistCount(query:$query)
       } 
   `, {
-    query: JSON.stringify([{
-      name: {$exists: true, $ne: ''},
-      tracks: {$exists: true, $ne: []},
-    }]),
-  });
-};
+  query: JSON.stringify([{
+    name: {$exists: true, $ne: ''},
+    tracks: {$exists: true, $ne: []},
+  }]),
+});
 
-export const createPlaylist = (playlistName) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-
-  return gql(`
+export const createPlaylist = (playlistName) => getGql(`
       mutation createPlaylist{
           PlaylistUpsert(playlist: {name: "${playlistName}"}){
               _id
           }
       }
   `);
-};
-
-export const addTracksToPlaylist = ({playlistId, arrayOfTracks}) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
 
-  return gql(`
+export const addTracksToPlaylist = ({playlistId, arrayOfTracks}) => getGql(`
       mutation createPlaylist{
           PlaylistUpsert(playlist: {
             _id: "${playlistId}"
@@ -97,22 +77,23 @@ export const addTracksToPlaylist = ({playlistId, arrayOfTracks}) => {
          }
       }
 `);
-};
-
-export const getUserPlaylist = (userId) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
 
-  return gql(`
+export const getUserPlaylist = ({userId, page = 1}) => getGql(`
       query findUserPlaylists($query: String){
           PlaylistFind(query:$query){
           _id name description
       }
     }
   `, {
-    query: JSON.stringify([{
+  query: JSON.stringify([
+    {
       ___owner: userId,
       name: {$exists: true, $ne: ''},
       tracks: {$exists: true, $ne: []},
-    }]),
-  });
-};
+    },
+    {
+      limit: [20],
+      skip: [(page - 1) * 20],
+    },
+  ]),
+});

+ 25 - 24
src/api/tracks.js

@@ -1,41 +1,42 @@
 import { getGql } from '../utils/getGql';
 import { BACKEND_URL } from '../constants';
 
-export const getTracksCount = () => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
+export const getTracksCount = () => getGql(`
       query getCount {
-       TrackCount(query:"[{}]")
+          TrackCount(query:"[{}]")
       } 
   `);
-};
 
-export const getTracksWithPage = (page = 1) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
+export const getTracksWithPage = (page = 1) => getGql(`
       query skipTracks($query: String) {
         TrackFind(query: $query) {
-       _id url originalFileName
+            _id url originalFileName
         }
       }
   `, {query: JSON.stringify([{}, {skip: [(page - 1) * 100]}])})
-    .then(data => data.map(track => ({
-      ...track,
-      url: `${BACKEND_URL}/${track.url}`,
-    })));
-};
+  .then(data => data.map(track => ({
+    ...track,
+    url: `${BACKEND_URL}/${track.url}`,
+  })));
 
-export const getUserTracks = (userId) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  return gql(`
+export const getUserTracks = ({userId, page = 1}) => getGql(`
       query findMyTracks($query: String){
             TrackFind(query: $query){
                  _id url originalFileName
             }
-        }
-  `, {query: JSON.stringify([{ ___owner: userId }])})
-    .then(data => data.map(track => ({
-      ...track,
-      url: `${BACKEND_URL}/${track.url}`,
-    })));
-};
+      }
+  `, {
+  query: JSON.stringify([
+    {___owner: userId},
+    {skip: [(page - 1) * 100]}]),
+})
+  .then(data => data.map(track => ({
+    ...track,
+    url: `${BACKEND_URL}/${track.url}`,
+  })));
+
+export const getUserTracksCount = (userId) => getGql(`
+      query getCount($query: String){
+           TrackCount(query: $query)
+      } 
+  `, {query: JSON.stringify([{___owner: userId}])});

+ 3 - 14
src/api/upload.js

@@ -1,11 +1,6 @@
 import { getGql } from '../utils/getGql';
-import { BACKEND_URL } from '../constants';
 
-export const setAvatar = ({avatarId, userId}) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  console.log(avatarId, userId);
-
-  return gql(`
+export const setAvatar = ({avatarId, userId}) => getGql(`
       mutation setAvatar{
            UserUpsert(user:{_id: "${userId}", avatar: {_id: "${avatarId}"}}){
                 _id, nick, avatar{
@@ -14,17 +9,11 @@ export const setAvatar = ({avatarId, userId}) => {
            }
       }
      `);
-};
-
-export const uploadTracks = (id) => {
-  const gql = getGql(`${BACKEND_URL}/graphql`);
-  console.log(id);
 
-  return gql(`
+export const uploadTracks = (id) => getGql(`
       mutation uploadTrack{
           TrackUpsert(track: {_id: "${id}"}){
                 _id, url, originalFileName
            }
       }
-     `);
-};
+  `);

+ 44 - 19
src/components/Header/Header.jsx

@@ -1,40 +1,65 @@
 import React from 'react';
 import './Header.scss';
-import { AccountCircle } from '@mui/icons-material';
-import UploadIcon from '@mui/icons-material/Upload';
 import {
-  AppBar, Toolbar, IconButton, Typography, Box, Button,
+  AppBar, Toolbar, IconButton, Box, Button, Avatar, Typography,
 } from '@mui/material';
 import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import LoginIcon from '@mui/icons-material/Login';
+import AudiotrackIcon from '@mui/icons-material/Audiotrack';
+import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
 import { ReactComponent as Vector } from '../../assets/svgs/Vector.svg';
+import { buildUrl } from '../../utils/buildUrl';
 
 export const Header = () => {
-  console.log(localStorage.getItem('authToken') ? './profile' : './login');
+  const user = useSelector(state => state.auth.user);
+  console.log(user);
+
   return (
     <AppBar position="static">
-      <Toolbar sx={{display: 'flex', justifyContent: 'space-between'}}>
-        <Vector className="logo"/>
+      <Toolbar sx={{display: 'flex', justifyContent: 'space-between', flexDirection: 'row'}}>
+        <Link to="/">
+          <Vector className="logo"/>
+        </Link>
         <Box sx={{
-          width: '50%', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
+          width: '20%',
+          display: 'flex',
+          justifyContent: 'space-around',
+          alignItems: 'center',
         }}
         >
           <Link to="/">
-            <Button variant="secondary">Main</Button>
+            <Button variant="secondary">
+              <AudiotrackIcon/>
+              <Typography variant="button" >Tracks</Typography>
+            </Button>
           </Link>
           <Link to="/playlists">
-            <Button variant="secondary">Playlists</Button>
-          </Link>
-          <Link
-            to="/profile"
-          >
-            <IconButton
-              size="large"
-              color="inherit"
-            >
-              <AccountCircle/>
-            </IconButton>
+            <Button variant="secondary">
+              <FormatListBulletedIcon/>
+              <Typography variant="button" marginLeft="5px">Playlists</Typography>
+            </Button>
           </Link>
         </Box>
+        <Link
+          to={user !== null ? '/profile' : '/login'}
+        >
+          <IconButton
+            size="large"
+            color="inherit"
+          >
+            {user !== null && user !== undefined
+              ? (
+                <Avatar
+                  className="avatar"
+                  sx={{width: 30, height: 30}}
+                  src={buildUrl(user?.avatar?.url ?? '')}
+                />
+              ) : (
+                <LoginIcon/>
+              )}
+          </IconButton>
+        </Link>
       </Toolbar>
     </AppBar>
   );

+ 0 - 48
src/components/PaginationControlled/PaginationControlled.jsx

@@ -1,48 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import {
-  Stack, Typography, Pagination, PaginationItem,
-} from '@mui/material';
-import { getGql } from '../../utils/getGql';
-import { BACKEND_URL } from '../../constants';
-
-export const PaginationControlled = (tracks) => {
-  const [page, setPage] = useState(1);
-  const [trackList, setTrackList] = useState(tracks);
-
-  useEffect(() => {
-    const gql = getGql(`${BACKEND_URL}/graphql`);
-    gql(`
-      query skipTracks($query: String) {
-        TrackFind(query: $query) {
-       _id url originalFileName
-        }
-      }
-  `, {query: JSON.stringify([{}, {skip: [100]}])})
-      .then(data => setTrackList(data.map(track => ({
-        ...track,
-        url: `${BACKEND_URL}/${track.url}`,
-      }))));
-  }, []);
-
-  const handleChange = (e) => {
-    setPage(page + 1);
-  };
-
-  return (
-    <Stack
-      spacing={2}
-      position="static"
-      bottom="0"
-      sx={{
-        display: 'flex',
-        alignItems: 'center',
-      }}
-    >
-      <Pagination
-        page={page}
-        count={10}
-        onClick={handleChange}
-      />
-    </Stack>
-  );
-};

+ 12 - 5
src/components/Player/Player.jsx

@@ -14,11 +14,13 @@ import {
 } from '../../store/types/playerTypes';
 
 const DEFAULT_VOLUME = 1;
+
 export const Player = () => {
   const playerState = useSelector(state => state.player);
   const dispatch = useDispatch();
+
   const [volume, setVolume] = useState(DEFAULT_VOLUME);
-  const [muted, setMuted] = useState(false);
+
   const trackIndex = playerState.trackList.findIndex(track => track._id === playerState.currentPlayingTrackId);
 
   const onVolumeChange = (e) => {
@@ -28,8 +30,13 @@ export const Player = () => {
   };
 
   const onMuted = () => {
-    setMuted(!muted);
-    !muted ? playerState.audio.volume = 0 : playerState.audio.volume = volume;
+    if (volume === 0) {
+      playerState.audio.volume = DEFAULT_VOLUME;
+      setVolume(DEFAULT_VOLUME);
+    } else {
+      playerState.audio.volume = 0;
+      setVolume(playerState.audio.volume);
+    }
   };
 
   const onBackward = () => {
@@ -119,9 +126,9 @@ export const Player = () => {
         }
       >
         <IconButton onClick={onMuted}>
-          {muted
+          {volume === 0
             ? (<VolumeOffIcon fontSize="large"/>)
-            : volume >= 0.1 && volume <= 0.5
+            : volume >= 0.01 && volume <= 0.5
               ? (<VolumeDown fontSize="large"/>)
               : volume === 0
                 ? (<VolumeOffIcon fontSize="large"/>)

+ 1 - 1
src/components/TrackList/TrackList.jsx

@@ -25,7 +25,7 @@ export const TrackList = ({tracks, isLoading}) => {
   };
 
   return isLoading ? (
-    <CircularProgress/>
+    <CircularProgress size="large"/>
   ) : (
     <Box sx={{
       minHeight: '70vh',

+ 31 - 0
src/helpers/index.jsx

@@ -0,0 +1,31 @@
+import React from 'react';
+
+export const saveState = state => {
+  try {
+    localStorage.setItem('state', JSON.stringify(state));
+  } catch (error) {
+    console.error('Can\'t save state to localStorage!');
+  }
+};
+
+export const stateToStorageSelector = state => ({
+  auth: state.auth,
+});
+
+export const loadState = () => {
+  try {
+    const serializedState = localStorage.getItem('state');
+
+    if (serializedState === null) {
+      return {};
+    }
+
+    const state = JSON.parse(serializedState);
+
+    saveState(state);
+
+    return state;
+  } catch (error) {
+    return {};
+  }
+};

+ 35 - 15
src/pages/MainPage/MainPage.jsx

@@ -1,30 +1,41 @@
 import React, { useEffect, useState } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 import {
-  Box, Button, ButtonGroup, Pagination, Stack,
+  Box, Button, ButtonGroup, Pagination, Stack, Typography,
 } from '@mui/material';
+import FileUploadIcon from '@mui/icons-material/FileUpload';
 import { TrackList } from '../../components/TrackList/TrackList';
 import { actionFetchTracks, actionFetchUserTracks } from '../../store/types/trackTypes';
-import { forwardToUploadPage } from '../../utils/history';
+import { forwardToPage } from '../../utils/history';
 
 export const MainPage = () => {
   const dispatch = useDispatch();
   const tracksState = useSelector(state => state.tracks);
+
   const [page, setPage] = useState(1);
+  const [selectedFilter, setSelectedFilter] = useState('all');
 
   useEffect(() => {
-    dispatch(actionFetchTracks(page));
+    if (selectedFilter === 'all') {
+      dispatch(actionFetchTracks(page));
+    } else if (selectedFilter === 'my') {
+      dispatch(actionFetchUserTracks(page));
+    }
   }, [page]);
 
   const handleChange = (e, value) => {
     setPage(value);
   };
 
-  const showMyTracks = () => {
-    dispatch(actionFetchUserTracks());
+  const showUserTracks = () => {
+    setSelectedFilter('my');
+    setPage(1);
+    dispatch(actionFetchUserTracks(page));
   };
 
   const showAllTracks = () => {
+    setSelectedFilter('all');
+    setPage(1);
     dispatch(actionFetchTracks(page));
   };
 
@@ -32,33 +43,42 @@ export const MainPage = () => {
     <Box>
       <ButtonGroup
         sx={{
-          mt: '10px',
+          m: '10px 30px',
           display: 'flex',
-          justifyContent: 'space-evenly',
+          justifyContent: 'space-between',
           alignItems: 'center',
         }}
       >
         <Box>
           <Button
-            onClick={() => forwardToUploadPage('/uploadTracks')}
+            onClick={() => forwardToPage('/uploadTracks')}
             variant="outlined"
           >
-            Upload tracks
+            <FileUploadIcon fontSize="small"/>
+            <Typography marginLeft="5px" variant="button">Upload tracks</Typography>
           </Button>
         </Box>
         <Box>
-          <Button onClick={showAllTracks}>
+          <Button
+            variant={selectedFilter === 'all' ? 'contained' : 'outlined'}
+            onClick={showAllTracks}
+          >
             Tracks
           </Button>
-          <Button onClick={showMyTracks}>
+          <Button
+            variant={selectedFilter === 'my' ? 'contained' : 'outlined'}
+            onClick={showUserTracks}
+          >
             My tracks
           </Button>
         </Box>
-        <Button
-          variant="outlined"
+        <Typography
+          variant="button"
+          color="primary"
+          cursor="default"
         >
-          {`Total: ${tracksState.totalCount}`}
-        </Button>
+          {`Total - ${tracksState.totalCount}`}
+        </Typography>
       </ButtonGroup>
       <TrackList
         tracks={tracksState.trackList}

+ 48 - 19
src/pages/PlaylistsPage/PlaylistsPage.jsx

@@ -1,22 +1,34 @@
 import React, { useEffect, useState } from 'react';
 import {
-  Box, Button, ButtonGroup, CircularProgress,
+  Box, Button, ButtonGroup,
   Grid, Pagination, Paper, Stack, Typography,
 } from '@mui/material';
 import { useDispatch, useSelector } from 'react-redux';
 import { Link } from 'react-router-dom';
+
+import AddBoxIcon from '@mui/icons-material/AddBox';
 import { actionFetchPlaylists, actionFetchUserPlaylists } from '../../store/types/playlistTypes';
 import { GenerateGradient } from '../../components/GenerateGradient/GenerateGradient';
-import { forwardToCreatePlaylistPage } from '../../utils/history';
+import { forwardToPage } from '../../utils/history';
 
 export const PlaylistsPage = () => {
   const dispatch = useDispatch();
   const state = useSelector(state => state.playlists);
   const {playlists, totalCount} = state;
+
   const [page, setPage] = useState(1);
+  const [selectedFilter, setSelectedFilter] = useState('all');
 
   useEffect(() => {
     dispatch(actionFetchPlaylists(page));
+  }, []);
+
+  useEffect(() => {
+    if (selectedFilter === 'all') {
+      dispatch(actionFetchPlaylists(page));
+    } else if (selectedFilter === 'my') {
+      dispatch(actionFetchUserPlaylists(page));
+    }
   }, [page]);
 
   const handleChange = (e, value) => {
@@ -24,10 +36,14 @@ export const PlaylistsPage = () => {
   };
 
   const showUserPlaylists = () => {
-    dispatch(actionFetchUserPlaylists());
+    setSelectedFilter('my');
+    setPage(1);
+    dispatch(actionFetchUserPlaylists(page));
   };
 
   const showAllPlaylists = () => {
+    setSelectedFilter('all');
+    setPage(1);
     dispatch(actionFetchPlaylists(page));
   };
 
@@ -35,50 +51,63 @@ export const PlaylistsPage = () => {
     <Box>
       <ButtonGroup
         sx={{
-          mt: '10px',
           display: 'flex',
-          justifyContent: 'space-evenly',
+          justifyContent: 'space-between',
           alignItems: 'center',
+          m: '10px 50px',
         }}
       >
-        <Box onClick={() => forwardToCreatePlaylistPage('/uploadPlaylist')}>
-          <Button variant="outlined">
-            Create playlist
+        <Box>
+          <Button variant="outlined" onClick={() => forwardToPage('/uploadPlaylist')}>
+            <AddBoxIcon fontSize="small"/>
+            <Typography marginLeft="5px" variant="button">Create playlist</Typography>
           </Button>
         </Box>
         <Box>
-          <Button onClick={showAllPlaylists}>
+          <Button
+            variant={selectedFilter === 'all' ? 'contained' : 'outlined'}
+            onClick={showAllPlaylists}
+          >
             Playlists
           </Button>
-          <Button onClick={showUserPlaylists}>
+          <Button
+            variant={selectedFilter === 'my' ? 'contained' : 'outlined'}
+            onClick={showUserPlaylists}
+          >
             My playlists
           </Button>
         </Box>
         <Box>
-          <Button
-            variant="outlined"
+          <Typography
+            variant="button"
+            color="primary"
+            cursor="default"
           >
-            {`Total:${state.totalCount}`}
-          </Button>
+            {`Total - ${state.totalCount}`}
+          </Typography>
         </Box>
       </ButtonGroup>
       <Grid
         spacing={3}
-        columns={12}
-        sx={{margin: '0', width: '100%'}}
+        columns={10}
+        sx={{
+          margin: '0',
+          width: '100%',
+          display: 'flex',
+          justifyContent: 'center',
+        }}
         container
       >
         {playlists.map(playlist => (
           <Grid
             key={playlist._id}
-            sx={{mb: '5%'}}
             item
           >
             <Link to={`/selectedPlaylist/${playlist._id}`}>
               <Paper
                 sx={{
                   minHeight: '30vh',
-                  width: '20vh',
+                  width: '25vh',
                   display: 'flex',
                   alignItems: 'center',
                   flexDirection: 'column',
@@ -127,7 +156,7 @@ export const PlaylistsPage = () => {
         sx={{
           display: 'flex',
           alignItems: 'center',
-          mb: '3%',
+          m: '3%',
         }}
       >
         <Pagination

+ 28 - 21
src/pages/ProfilePage/ProfilePage.jsx

@@ -1,18 +1,20 @@
 import React, { useCallback, useEffect, useState } from 'react';
 import {
-  Avatar, Box, Button, Grid, Input, InputAdornment, Paper, TextField, Typography, useTheme,
+  Avatar, Box, Button, Grid, Input, InputAdornment, Paper, Typography,
 } from '@mui/material';
 import { useDispatch, useSelector } from 'react-redux';
 import EditIcon from '@mui/icons-material/Edit';
 import { useDropzone } from 'react-dropzone';
 import AddAPhotoIcon from '@mui/icons-material/AddAPhoto';
 import CheckBoxIcon from '@mui/icons-material/CheckBox';
-import { actionFindUserById, actionSetNick } from '../../store/types/authTypes';
+import {
+  actionFindUserById, actionLogOut, actionSetNick,
+} from '../../store/types/authTypes';
 import { history } from '../../createHistory';
 import { jwtDecode } from '../../utils/jwtDecode';
 import { actionSetUploadFile } from '../../store/types/uploadTypes';
-import { BACKEND_URL } from '../../constants';
 import './ProfilePage.scss';
+import { buildUrl } from '../../utils/buildUrl';
 
 export const ProfilePage = () => {
   const dispatch = useDispatch();
@@ -28,6 +30,7 @@ export const ProfilePage = () => {
   const {getRootProps, getInputProps} = useDropzone({onDrop});
 
   useEffect(() => {
+    // findUser();
     if (user === null && localStorage.getItem('authToken') !== null) {
       const token = jwtDecode(localStorage.getItem('authToken'));
       const {id} = token.sub;
@@ -37,23 +40,20 @@ export const ProfilePage = () => {
     }
   }, []);
 
-  console.log(user?.nick);
-
   useEffect(() => {
     setCurrentNick(user?.nick);
   }, [user?.nick]);
 
   const logOut = () => {
     localStorage.removeItem('authToken');
-    if (localStorage.getItem('authToken') === null) {
-      history.push('/login');
-    }
+    dispatch(actionLogOut());
+    history.push('/login');
   };
 
   const onChangeNick = (e) => {
     setCurrentNick(e.target.value);
   };
-  console.log(user);
+
   const closeAndGetChangedNick = () => {
     setOpenNick(!openNick);
     console.log(currentNick, user);
@@ -107,18 +107,25 @@ export const ProfilePage = () => {
                 </Avatar>
               </Box>
               <Box>
-                <Avatar
-                  className="avatar"
-                  onMouseEnter={() => {
-                    setIsHovered(!isHovered);
-                  }}
-                  sx={{width: 100, height: 100}}
-                  src={
-                    user !== null
-                      ? (`${BACKEND_URL}/${user?.avatar?.url}`)
-                      : null
-                  }
-                />
+                {user?.avatar?.url !== null
+                  ? (
+                    <Avatar
+                      className="avatar"
+                      onMouseEnter={() => {
+                        setIsHovered(!isHovered);
+                      }}
+                      sx={{width: 100, height: 100}}
+                      src={buildUrl(user?.avatar?.url ?? '')}
+                    />
+                  ) : (
+                    <Avatar
+                      className="avatar"
+                      onMouseLeave={() => setIsHovered(!isHovered)}
+                      sx={{width: 100, height: 100}}
+                    >
+                      <AddAPhotoIcon fontSize="large" color="primary"/>
+                    </Avatar>
+                  )}
               </Box>
             </Box>
           </Grid>

+ 2 - 15
src/store/reducers/authReducer.js

@@ -30,32 +30,19 @@ export function authReducer(state = initialState, action) {
         ...state,
         errorMessage: action.payload,
       };
-    case types.FETCH_FIND_USER_BY_ID:
-      return {
-        ...state,
-      };
     case types.FETCH_FIND_USER_BY_ID_SUCCESS:
       return {
         ...state,
         user: action.payload,
       };
-    case types.FETCH_FIND_USER_BY_ID_FAIL:
-      return {
-        ...state,
-      };
     case types.SET_USER:
-      console.log(action.payload);
       return {
         ...state,
+        login: action.payload,
+        authToken: action.payload,
         user: action.payload,
       };
-    case types.SET_NICK:
-      console.log(action.payload);
-      return {
-        ...state,
-      };
     case types.SET_NICK_SUCCESS:
-      console.log(action.payload);
       return {
         ...state,
         user: action.payload,

+ 0 - 1
src/store/reducers/playerReducer.js

@@ -1,5 +1,4 @@
 import types from '../types/playerTypes';
-import { BACKEND_URL } from '../../constants';
 
 const initialState = {
   trackList: [],

+ 0 - 18
src/store/reducers/playlistsReducer.js

@@ -21,11 +21,6 @@ export function playlistsReducer(state = initialState, action) {
         totalCount: action.payload.playlistsCount,
         playlists: action.payload.playlists,
       };
-    case
-      types.FETCH_PLAYLISTS_FAIL:
-      return {
-        ...state,
-      };
     case types.FETCH_ONE_PLAYLIST:
       return {
         ...state,
@@ -37,25 +32,12 @@ export function playlistsReducer(state = initialState, action) {
         isLoading: false,
         selectedPlaylist: action.payload,
       };
-    case types.FETCH_ONE_PLAYLIST_FAIL:
-      return {
-        ...state,
-      };
-    case types.CREATE_PLAYLIST:
-      return {
-        ...state,
-      };
     case types.CREATE_PLAYLIST_BY_ID_SUCCESS:
       return {
         ...state,
         selectedPlaylist: action.payload,
       };
-    case types.FETCH_USER_PLAYLISTS:
-      return {
-        ...state,
-      };
     case types.FETCH_USER_PLAYLISTS_SUCCESS:
-      console.log(action.payload);
       return {
         ...state,
         playlists: action.payload.userPlaylists,

+ 13 - 16
src/store/sagas/authSaga.js

@@ -8,12 +8,12 @@ import types, {
   actionRegisterFail,
   actionLogin,
   actionFindUserByIdSuccess,
-  actionFindUserByIdFail, actionSetUser, actionSetNickSuccess,
+  actionSetUser, actionSetNickSuccess,
 } from '../types/authTypes';
 import {
   findUserById, login, registration, setNick,
 } from '../../api/auth';
-import { forwardToMainPage } from '../../utils/history';
+import { forwardToPage } from '../../utils/history';
 import { jwtDecode } from '../../utils/jwtDecode';
 
 function* loginWorker(action) {
@@ -32,7 +32,7 @@ function* loginWorker(action) {
     yield put(actionFindUserByIdSuccess(user));
     yield put(actionSetUser(user));
 
-    yield call(forwardToMainPage, '/');
+    yield call(forwardToPage, '/');
   } catch (e) {
     yield put(actionLoginFail(e.message));
   }
@@ -52,24 +52,20 @@ function* registerWorker(action) {
 }
 
 function* findUserWorker(action) {
-  try {
-    const user = yield call(findUserById, action.payload);
-    yield put(actionFindUserByIdSuccess(user));
-  } catch (e) {
-    yield put(actionFindUserByIdFail());
-  }
+  const user = yield call(findUserById, action.payload);
+  yield put(actionFindUserByIdSuccess(user));
 }
 
 function* setNickWorker(action) {
   const userId = yield select(state => state.auth.user._id);
+  const response = yield call(setNick, {id: userId, nick: action.payload});
 
-  try {
-    const response = yield call(setNick, {id: userId, nick: action.payload });
-    yield put(actionSetNickSuccess(response));
-    yield put(actionSetUser(response));
-  } catch (e) {
-    e.message;
-  }
+  yield put(actionSetNickSuccess(response));
+  yield put(actionSetUser(response));
+}
+
+function* logOutWorker(action) {
+  yield put(actionSetUser());
 }
 
 export function* authSaga() {
@@ -77,4 +73,5 @@ export function* authSaga() {
   yield takeLatest(types.FETCH_REGISTER, registerWorker);
   yield takeLatest(types.FETCH_FIND_USER_BY_ID, findUserWorker);
   yield takeLatest(types.SET_NICK, setNickWorker);
+  yield takeLatest(types.LOG_OUT, logOutWorker);
 }

+ 2 - 1
src/store/sagas/playlistsSaga.js

@@ -55,8 +55,9 @@ function* createUserPlaylistWorker(action) {
 }
 
 function* fetchUserPlaylistsWorker(action) {
+  const page = action.payload;
   const userId = yield select(state => state?.auth?.user?._id);
-  const userPlaylists = yield call(getUserPlaylist, userId);
+  const userPlaylists = yield call(getUserPlaylist, {userId, page});
 
   yield put(actionFetchUserPlaylistsSuccess({userPlaylists, totalCount: userPlaylists.length}));
 }

+ 6 - 3
src/store/sagas/tracksSaga.js

@@ -7,7 +7,7 @@ import types, {
   actionFetchTracksSuccess,
 } from '../types/trackTypes';
 import {
-  getTracksCount, getTracksWithPage, getUserTracks,
+  getTracksCount, getTracksWithPage, getUserTracks, getUserTracksCount,
 } from '../../api/tracks';
 
 function* fetchTracksWorker(action) {
@@ -22,10 +22,13 @@ function* fetchTracksWorker(action) {
 }
 
 function* fetchUserTracksWorker(action) {
+  const page = action.payload;
   const userId = yield select(state => state?.auth?.user?._id);
-  const userTracks = yield call(getUserTracks, userId);
 
-  yield put(actionFetchUserTracksSuccess({userTracks, totalCount: userTracks.length}));
+  const userTracks = yield call(getUserTracks, {userId, page});
+  const userTracksCount = yield call(getUserTracksCount, userId);
+
+  yield put(actionFetchUserTracksSuccess({userTracks, totalCount: userTracksCount}));
 }
 
 export function* tracksSaga() {

+ 4 - 8
src/store/sagas/uploadSaga.js

@@ -4,7 +4,7 @@ import {
 import types, {
   actionSetUploadTrackSuccess,
 } from '../types/uploadTypes';
-import { getGqlForUpload, getGqlForUploadTracks } from '../../utils/getGqlForUpload';
+import { getGqlForUpload } from '../../utils/getGqlForUpload';
 import { jwtDecode } from '../../utils/jwtDecode';
 import { setAvatar } from '../../api/upload';
 import { actionSetUser } from '../types/authTypes';
@@ -13,13 +13,14 @@ function* uploadFileWorker(action) {
   const auth = yield select(state => state.auth.authToken);
 
   try {
-    const response = yield call(getGqlForUpload, action.payload);
+    const response = yield call(getGqlForUpload, {data: action.payload, formName: 'photo', fetchPart: 'upload'});
     const avatarId = response._id;
 
     const token = yield call(jwtDecode, auth);
     const userId = token.sub.id;
 
     const result = yield call(setAvatar, {userId, avatarId});
+    console.log(result);
     yield put(actionSetUser(result));
   } catch (e) {
     e.message;
@@ -28,14 +29,9 @@ function* uploadFileWorker(action) {
 
 function* uploadTrackWorker(action) {
   try {
-    const response = yield call(getGqlForUploadTracks, action.payload);
-    const trackId = response._id;
-    console.log(response);
+    const response = yield call(getGqlForUpload, {data: action.payload, formName: 'track', fetchPart: 'track'});
 
     yield put(actionSetUploadTrackSuccess({...response, originalFileName: action.payload.name}));
-
-    // const result = yield call(uploadTracks, trackId);
-    // console.log(result);
   } catch (e) {
     e.message;
   }

+ 6 - 0
src/store/store.js

@@ -13,6 +13,7 @@ import { playlistsReducer } from './reducers/playlistsReducer';
 import { playlistsSaga } from './sagas/playlistsSaga';
 import { uploadSaga } from './sagas/uploadSaga';
 import { uploadReducer } from './reducers/uploadReducer';
+import { loadState, saveState, stateToStorageSelector } from '../helpers';
 
 const sagaMiddleware = createSagaMiddleware();
 
@@ -36,12 +37,17 @@ function* rootSaga() {
 
 const store = createStore(
   rootReducer,
+  loadState(),
   compose(
     applyMiddleware(sagaMiddleware),
     window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
   ),
 );
 
+store.subscribe(() => {
+  saveState(stateToStorageSelector(store.getState()));
+});
+
 sagaMiddleware.run(rootSaga);
 
 export default store;

+ 3 - 1
src/store/types/authTypes.js

@@ -11,6 +11,7 @@ const types = {
   SET_USER: 'SET_USER',
   SET_NICK: 'SET_NICK',
   SET_NICK_SUCCESS: 'SET_NICK_SUCCESS',
+  LOG_OUT: 'LOG_OUT',
 };
 
 export const actionLogin = (payload) => ({type: types.FETCH_LOGIN, payload});
@@ -26,8 +27,9 @@ export const actionFindUserByIdSuccess = (payload) => ({type: types.FETCH_FIND_U
 export const actionFindUserByIdFail = (payload) => ({type: types.FETCH_FIND_USER_BY_ID_FAIL, payload});
 
 export const actionSetUser = (payload) => ({type: types.SET_USER, payload});
-
 export const actionSetNick = (payload) => ({type: types.SET_NICK, payload});
 export const actionSetNickSuccess = (payload) => ({type: types.SET_NICK_SUCCESS, payload});
 
+export const actionLogOut = (payload) => ({type: types.LOG_OUT, payload});
+
 export default types;

+ 1 - 4
src/store/types/playerTypes.js

@@ -3,7 +3,6 @@ const types = {
   PLAY: 'PLAY',
   SET_AUDIO: 'SET_AUDIO',
   SET_CURRENT_TRACK_ID: 'SET_CURRENT_TRACK_ID',
-  // SET_UPLOAD_FILE: 'SET_UPLOAD_FILE',
   SET_PLAYER_STATE: 'SET_PLAYER_STATE',
   PREVIOUS_TRACK: 'PREVIOUS_TRACK',
   NEXT_TRACK: 'NEXT_TRACK',
@@ -12,10 +11,8 @@ const types = {
 
 export const actionPause = (payload) => ({type: types.PAUSE, payload});
 export const actionPlay = (payload) => ({type: types.PLAY, payload});
-// export const actionSetAudio = (payload) => ({type: types.SET_AUDIO, payload});
-// export const actionSetCurrentTrackId = (payload) => ({type: types.SET_CURRENT_TRACK_ID, payload});
-// export const actionUploadFile = (payload) => ({type: types.SET_UPLOAD_FILE, payload});
 export const actionSetPlayerState = (payload) => ({type: types.SET_PLAYER_STATE, payload});
+
 export const actionPreviousTrack = (payload) => ({type: types.PREVIOUS_TRACK, payload});
 export const actionNextTrack = (payload) => ({type: types.NEXT_TRACK, payload});
 export const actionChangeTime = (payload) => ({type: types.CHANGE_TIME, payload});

+ 3 - 0
src/utils/buildUrl.js

@@ -0,0 +1,3 @@
+import { BACKEND_URL } from '../constants';
+
+export const buildUrl = (urlPart) => `${BACKEND_URL}/${urlPart}`;

+ 4 - 1
src/utils/getGql.js

@@ -1,4 +1,7 @@
-export const getGql = url => (query, variables = {}) => fetch(url, {
+import { BACKEND_URL } from '../constants';
+import { buildUrl } from './buildUrl';
+
+export const getGql = (query, variables = {}) => fetch(buildUrl('graphql'), {
   method: 'POST',
   headers: {
     'Content-Type': 'application/json',

+ 4 - 20
src/utils/getGqlForUpload.js

@@ -1,27 +1,11 @@
 import { BACKEND_URL } from '../constants';
 
-export const getGqlForUpload = (data) => {
+export const getGqlForUpload = ({data, formName, fetchPart}) => {
   const formData = new FormData();
-  formData.append('photo', data);
-  console.log(data);
-  return (fetch(`${BACKEND_URL}/upload`, {
-    method: 'POST',
-    headers: {
-      ...(localStorage.authToken ? {'Authorization': `Bearer ${localStorage.authToken}`}
-        : {}),
-    },
-    body: formData,
-  }))
-    .then((response) => response.json());
-};
-
-export const getGqlForUploadTracks = (data) => {
-  console.log(data);
-  const formData = new FormData();
-  formData.append('track', data);
-  console.log(formData, data);
+  formData.append(formName, data);
+  console.log(data, formName, fetchPart);
 
-  return (fetch(`${BACKEND_URL}/track`, {
+  return (fetch(`${BACKEND_URL}/${fetchPart}`, {
     method: 'POST',
     headers: {
       ...(localStorage.authToken ? {'Authorization': `Bearer ${localStorage.authToken}`}

+ 1 - 13
src/utils/history.js

@@ -1,18 +1,6 @@
 import { history } from '../createHistory';
 
-export const forwardToMainPage = (location) => {
-  if (localStorage.getItem('authToken')) {
-    history.push(location);
-  }
-};
-
-export const forwardToUploadPage = (location) => {
-  if (localStorage.getItem('authToken')) {
-    history.push(location);
-  }
-};
-
-export const forwardToCreatePlaylistPage = (location) => {
+export const forwardToPage = (location) => {
   if (localStorage.getItem('authToken')) {
     history.push(location);
   }

+ 3 - 9
src/utils/jwtDecode.js

@@ -1,14 +1,8 @@
-// eslint-disable-next-line consistent-return
 export const jwtDecode = (token) => {
   try {
-    let decoded = token.split('.');
-    // eslint-disable-next-line prefer-destructuring
-    decoded = decoded[1];
-    decoded = atob(decoded);
-    decoded = JSON.parse(decoded);
-    console.log(decoded);
-    return decoded;
+    const [, decodedRaw] = token.split('.');
+    return JSON.parse(atob(decodedRaw));
   } catch (e) {
-    e.message;
+    throw new Error(e.message);
   }
 };