4 Commits c24a70e618 ... c87ad6a15d

Author SHA1 Message Date
  Антон Задорожный c87ad6a15d Profile page reworked, start of development of all tracks page 2 years ago
  surstrommed c24a70e618 First upload of project 2 years ago
  surstrommed 5288f7388a Upload the project 2 years ago
  Bonyant 8ec949553a Initialize project using Create React App 2 years ago

File diff suppressed because it is too large
+ 40 - 17031
package-lock.json


+ 1 - 1
src/App.scss

@@ -25,7 +25,7 @@ body {
     }
   }
   .content {
-    flex: 0 0 100%;
+    flex: 0 0 50%;
   }
 }
 

+ 194 - 175
src/actions/index.js

@@ -1,175 +1,194 @@
-import { backURL, gql } from "../helpers";
-
-export const actionPending = (name) => ({
-  type: "PROMISE",
-  status: "PENDING",
-  name,
-});
-
-export const actionResolved = (name, payload) => ({
-  type: "PROMISE",
-  status: "RESOLVED",
-  name,
-  payload,
-});
-
-export const actionRejected = (name, error) => ({
-  type: "PROMISE",
-  status: "REJECTED",
-  name,
-  error,
-});
-
-export const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
-
-export const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
-
-export const actionPromise = (name, promise) => async (dispatch) => {
-  dispatch(actionPending(name));
-  try {
-    let data = await promise;
-    dispatch(actionResolved(name, data));
-    return data;
-  } catch (error) {
-    dispatch(actionRejected(name, error));
-  }
-};
-
-const actionLogin = (login, password) =>
-  actionPromise(
-    "login",
-    gql(
-      `query log($login:String!, $password:String!){
-        login(login:$login, password: $password)
-      }`,
-      { login, password }
-    )
-  );
-
-const actionRegister = (login, password) =>
-  actionPromise(
-    "registration",
-    gql(
-      `mutation reg($login:String!, $password:String!) {
-        createUser(login:$login, password: $password) {
-        _id login nick
-    }
-  }
-  `,
-      { login, password }
-    )
-  );
-
-export const actionFindUser = (_id) =>
-  actionPromise(
-    "user",
-    gql(
-      `query findUser($q:String){
-        UserFindOne(query:$q){
-          _id login nick createdAt avatar {
-            _id url
-          }
-        }
-      }
-  `,
-      { q: JSON.stringify([{ _id }]) }
-    )
-  );
-
-export const actionAboutMe = () => async (dispatch, getState) => {
-  let { id } = getState().auth.payload.sub;
-  await dispatch(actionFindUser(id));
-};
-
-export const actionFullLogin = (login, password) => async (dispatch) => {
-  let token = await dispatch(actionLogin(login, password));
-  if (token) {
-    await dispatch(actionAuthLogin(token));
-    await dispatch(actionAboutMe());
-  }
-};
-
-export const actionFullRegister = (login, password) => async (dispatch) => {
-  let check = await dispatch(actionRegister(login, password));
-  if (check) {
-    dispatch(actionFullLogin(login, password));
-  }
-};
-
-export const actionFindTracks = () =>
-  actionPromise(
-    "tracks",
-    gql(
-      `query findTracks($q:String){
-        TrackFind(query:$q){
-          _id url owner {
-            _id login nick
-          }
-        }
-      }
-  `,
-      { q: "[{}]" }
-    )
-  );
-
-export const actionFindUsers = () =>
-  actionPromise(
-    "users",
-    gql(
-      `query findUsers($q:String){
-        UserFind(query: $q){
-          _id login nick avatar {
-            _id url
-          }
-        }
-      }
-  `,
-      { q: "[{}]" }
-    )
-  );
-
-export const actionUserUpdate = ({ _id, login, password, nick, avatar }) =>
-  actionPromise(
-    "userUpdate",
-    gql(
-      `mutation userUpdate($user:UserInput){
-        UserUpsert(user: $user){
-          _id login nick
-        }
-      }
-  `,
-      {
-        user: {
-          _id,
-          login,
-          password,
-          nick,
-          avatar,
-        },
-      }
-    )
-  );
-
-const actionUploadPhoto = (file) => async (dispatch) => {
-  let fd = new FormData();
-  fd.append("photo", file);
-  await dispatch(
-    actionPromise(
-      "uploadPhoto",
-      fetch(`${backURL}/upload`, {
-        method: "POST",
-        headers: localStorage.authToken
-          ? { Authorization: "Bearer " + localStorage.authToken }
-          : {},
-        body: fd,
-      }).then((res) => res.json())
-    )
-  );
-};
-
-export const actionSetAvatar = (file) => async (dispatch, getState) => {
-  await dispatch(actionUploadPhoto(file));
-  let { _id } = getState().promise.uploadFile.payload;
-  let { id } = getState().auth.payload.sub;
-  await dispatch(actionUserUpdate({ _id: id, avatar: { _id } }));
-};
+import { backURL, gql } from "../helpers";
+
+export const actionPending = (name) => ({
+  type: "PROMISE",
+  status: "PENDING",
+  name,
+});
+
+export const actionResolved = (name, payload) => ({
+  type: "PROMISE",
+  status: "RESOLVED",
+  name,
+  payload,
+});
+
+export const actionRejected = (name, error) => ({
+  type: "PROMISE",
+  status: "REJECTED",
+  name,
+  error,
+});
+
+export const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
+
+export const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
+
+export const actionPromise = (name, promise) => async (dispatch) => {
+  dispatch(actionPending(name));
+  try {
+    let data = await promise;
+    dispatch(actionResolved(name, data));
+    return data;
+  } catch (error) {
+    dispatch(actionRejected(name, error));
+  }
+};
+
+export const actionUserUpdate = ({ _id, login, password, nick, avatar }) =>
+  actionPromise(
+    "userUpdate",
+    gql(
+      `mutation userUpdate($user:UserInput){
+        UserUpsert(user: $user){
+          _id login nick avatar {
+            _id, url
+          }
+        }
+      }
+  `,
+      {
+        user: {
+          _id,
+          login,
+          password,
+          nick,
+          avatar,
+        },
+      }
+    )
+  );
+
+export const actionChangePassword = (login, password, newPassword) =>
+  actionPromise(
+    "changePassword",
+    gql(
+      `query changePass($login:String!, $password:String!, $newPassword:String!){
+        changePassword(login:$login, password: $password, newPassword: $newPassword)
+      }`,
+      { login, password, newPassword }
+    )
+  );
+
+const actionLogin = (login, password) =>
+  actionPromise(
+    "login",
+    gql(
+      `query log($login:String!, $password:String!){
+        login(login:$login, password: $password)
+      }`,
+      { login, password }
+    )
+  );
+
+const actionRegister = (login, password) =>
+  actionPromise(
+    "registration",
+    gql(
+      `mutation reg($login:String!, $password:String!) {
+        createUser(login:$login, password: $password) {
+        _id login
+    }
+  }
+  `,
+      { login, password }
+    )
+  );
+
+export const actionFindUser = (_id) =>
+  actionPromise(
+    "user",
+    gql(
+      `query findUser($q:String){
+        UserFindOne(query:$q){
+          _id login nick createdAt avatar {
+            _id url
+          }
+        }
+      }
+  `,
+      { q: JSON.stringify([{ _id }]) }
+    )
+  );
+
+export const actionAboutMe = () => async (dispatch, getState) => {
+  let { id } = getState().auth.payload.sub;
+  await dispatch(actionFindUser(id));
+};
+
+export const actionFullLogin = (l, p) => async (dispatch) => {
+  let token = await dispatch(actionLogin(l, p));
+  if (token) {
+    await dispatch(actionAuthLogin(token));
+    await dispatch(actionAboutMe());
+  }
+};
+
+export const actionFullRegister = (l, p) => 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(actionUserUpdate({ _id, nick }));
+    await dispatch(actionFullLogin(l, p));
+  }
+};
+
+export const actionFindTracks = () =>
+  actionPromise(
+    "tracks",
+    gql(
+      `query findTracks($q:String){
+        TrackFind(query:$q){
+          _id url owner {
+            _id login nick
+          }
+        }
+      }
+  `,
+      { q: "[{}]" }
+    )
+  );
+
+export const actionFindUsers = () =>
+  actionPromise(
+    "users",
+    gql(
+      `query findUsers($q:String){
+        UserFind(query: $q){
+          _id login nick avatar {
+            _id url
+          }
+        }
+      }
+  `,
+      { q: "[{}]" }
+    )
+  );
+
+const actionUploadPhoto = (file) => {
+  let fd = new FormData();
+  fd.append("photo", file);
+  return actionPromise(
+    "uploadPhoto",
+    fetch(`${backURL}/upload`, {
+      method: "POST",
+      headers: localStorage.authToken
+        ? { Authorization: "Bearer " + localStorage.authToken }
+        : {},
+      body: fd,
+    }).then((res) => res.json())
+  );
+};
+
+export const actionSetAvatar = (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());
+};

+ 1 - 0
src/components/Audio.js

@@ -0,0 +1 @@
+export const Audio = () => {};

+ 23 - 23
src/components/AuthCheck.js

@@ -1,23 +1,23 @@
-import { Alert } from "react-bootstrap";
-import { Link } from "react-router-dom";
-
-export const AuthCheck = ({ header }) => {
-  return (
-    <div>
-      <Alert>
-        <h2>{header}</h2>
-        <p>
-          Чтобы видеть треки других пользователей, ваши треки, ваш профиль и
-          остальное необходимо войти в аккаунт. Если у вас ещё нет аккаунта -
-          зарегистрируйтесь!
-        </p>
-        <Link to="/signup" className="btn btn-outline-primary">
-          Зарегистрироваться
-        </Link>{" "}
-        <Link to="/login" className="btn btn-outline-primary">
-          Войти
-        </Link>
-      </Alert>
-    </div>
-  );
-};
+import { Alert } from "react-bootstrap";
+import { Link } from "react-router-dom";
+
+export const AuthCheck = ({ header }) => {
+  return (
+    <div>
+      <Alert>
+        <h2>{header}</h2>
+        <p>
+          Чтобы видеть треки других пользователей, ваши треки, ваш профиль и
+          остальное необходимо войти в аккаунт. Если у вас ещё нет аккаунта -
+          зарегистрируйтесь!
+        </p>
+        <Link to="/signup" className="btn btn-outline-primary">
+          Зарегистрироваться
+        </Link>{" "}
+        <Link to="/login" className="btn btn-outline-primary">
+          Войти
+        </Link>
+      </Alert>
+    </div>
+  );
+};

+ 4 - 1
src/components/Dropzone.js

@@ -18,7 +18,10 @@ const MyDropzone = ({ onload }) => {
       {isDragActive ? (
         <p>Поместите файлы сюда...</p>
       ) : (
-        <p>Перетащите файлы сюда или нажмите на поле и выберите файлы</p>
+        <p>
+          Для загрузки перетащите файлы сюда или нажмите на поле и выберите
+          файлы
+        </p>
       )}
     </div>
   );

+ 35 - 35
src/components/Header.js

@@ -1,35 +1,35 @@
-import React from "react";
-import { Navbar, Container, Nav, Button } from "react-bootstrap";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
-import { CAuth } from "../pages/Auth";
-import { history } from "./../App";
-
-export const Header = () => {
-  return (
-    <Navbar
-      className="Header"
-      collapseOnSelect
-      expand="lg"
-      bg="dark"
-      variant="dark"
-    >
-      <Container>
-        <Navbar.Toggle aria-controls="responsive-navbar-nav" />
-        <Navbar.Collapse id="responsive-navbar-nav">
-          <Nav className="me-auto d-flex mx-auto">
-            <Button className="btn-circle" onClick={() => history.goBack()}>
-              <FontAwesomeIcon icon={faArrowLeft} />
-            </Button>
-            <Button className="btn-circle" onClick={() => history.goForward()}>
-              <FontAwesomeIcon icon={faArrowRight} />
-            </Button>
-          </Nav>
-          <Nav>
-            <CAuth />
-          </Nav>
-        </Navbar.Collapse>
-      </Container>
-    </Navbar>
-  );
-};
+import React from "react";
+import { Navbar, Container, Nav, Button } from "react-bootstrap";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
+import { CAuth } from "../pages/Auth";
+import { history } from "./../App";
+
+export const Header = () => {
+  return (
+    <Navbar
+      className="Header"
+      collapseOnSelect
+      expand="lg"
+      bg="dark"
+      variant="dark"
+    >
+      <Container>
+        <Navbar.Toggle aria-controls="responsive-navbar-nav" />
+        <Navbar.Collapse id="responsive-navbar-nav">
+          <Nav className="me-auto d-flex mx-auto">
+            <Button className="btn-circle" onClick={() => history.goBack()}>
+              <FontAwesomeIcon icon={faArrowLeft} />
+            </Button>
+            <Button className="btn-circle" onClick={() => history.goForward()}>
+              <FontAwesomeIcon icon={faArrowRight} />
+            </Button>
+          </Nav>
+          <Nav>
+            <CAuth />
+          </Nav>
+        </Navbar.Collapse>
+      </Container>
+    </Navbar>
+  );
+};

+ 34 - 34
src/components/Main.js

@@ -1,34 +1,34 @@
-import React from "react";
-import { Route, Switch, withRouter } from "react-router-dom";
-import { CLoginForm } from "../pages/Login";
-import { CSignUpForm } from "../pages/Register";
-import { Page404 } from "../pages/Page404";
-import { CSearch } from "./../pages/Search";
-import { CLibrary } from "./../pages/Library";
-import { CProfile } from "./../pages/Profile";
-
-const Content = ({ children }) => <div className="Content">{children}</div>;
-
-const PageMain = () => {
-  return (
-    <div className="MainContent">
-      <h1>Главная страница</h1>
-    </div>
-  );
-};
-
-export const Main = () => (
-  <main className="Main">
-    <Content>
-      <Switch>
-        <Route path="/" component={withRouter(PageMain)} exact />
-        <Route path="/login" component={withRouter(CLoginForm)} />
-        <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="" component={withRouter(Page404)} />
-      </Switch>
-    </Content>
-  </main>
-);
+import React from "react";
+import { Route, Switch, withRouter } from "react-router-dom";
+import { CLoginForm } from "../pages/Login";
+import { CSignUpForm } from "../pages/Register";
+import { Page404 } from "../pages/Page404";
+import { CSearch } from "./../pages/Search";
+import { CLibrary } from "./../pages/Library";
+import { CProfile } from "./../pages/Profile";
+
+const Content = ({ children }) => <div className="Content">{children}</div>;
+
+const PageMain = () => {
+  return (
+    <div className="MainContent">
+      <h1>Главная страница</h1>
+    </div>
+  );
+};
+
+export const Main = () => (
+  <main className="Main">
+    <Content>
+      <Switch>
+        <Route path="/" component={withRouter(PageMain)} exact />
+        <Route path="/login" component={withRouter(CLoginForm)} />
+        <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="" component={withRouter(Page404)} />
+      </Switch>
+    </Content>
+  </main>
+);

+ 20 - 20
src/components/SearchField.js

@@ -1,20 +1,20 @@
-import { Button } from "react-bootstrap";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faSearch } from "@fortawesome/free-solid-svg-icons";
-
-export const SearchField = () => {
-  return (
-    <div className="input-group rounded">
-      <input
-        type="search"
-        className="form-control rounded"
-        placeholder="Музыка, пользователи..."
-        aria-label="Поиск"
-        aria-describedby="search-addon"
-      />
-      <Button variant="primary" id="search-addon">
-        <FontAwesomeIcon icon={faSearch} />
-      </Button>
-    </div>
-  );
-};
+import { Button } from "react-bootstrap";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faSearch } from "@fortawesome/free-solid-svg-icons";
+
+export const SearchField = () => {
+  return (
+    <div className="input-group rounded">
+      <input
+        type="search"
+        className="form-control rounded"
+        placeholder="Музыка, пользователи..."
+        aria-label="Поиск"
+        aria-describedby="search-addon"
+      />
+      <Button variant="primary" id="search-addon">
+        <FontAwesomeIcon icon={faSearch} />
+      </Button>
+    </div>
+  );
+};

+ 51 - 51
src/components/Sidebar.js

@@ -1,51 +1,51 @@
-import {
-  CDBSidebar,
-  CDBSidebarContent,
-  CDBSidebarFooter,
-  CDBSidebarHeader,
-  CDBSidebarMenu,
-  CDBSidebarMenuItem,
-} from "cdbreact";
-import { NavLink } from "react-router-dom";
-import { Logo } from "./Logo";
-
-export const Sidebar = () => {
-  return (
-    <div
-      style={{
-        display: "flex",
-        height: "100vh",
-        overflow: "scroll initial",
-        top: 0,
-        position: "fixed",
-      }}
-    >
-      <CDBSidebar backgroundColor="#212529">
-        <CDBSidebarHeader prefix={<i className="fa fa-bars fa-large"></i>}>
-          <Logo />
-        </CDBSidebarHeader>
-        <CDBSidebarContent className="sidebar-content">
-          <CDBSidebarMenu>
-            <NavLink exact to="/" activeClassName="activeClicked">
-              <CDBSidebarMenuItem icon="columns">Главная</CDBSidebarMenuItem>
-            </NavLink>
-            <NavLink exact to="/search" activeClassName="activeClicked">
-              <CDBSidebarMenuItem icon="search">Поиск</CDBSidebarMenuItem>
-            </NavLink>
-            <NavLink exact to="/library" activeClassName="activeClicked">
-              <CDBSidebarMenuItem icon="user">Моя музыка</CDBSidebarMenuItem>
-            </NavLink>
-          </CDBSidebarMenu>
-        </CDBSidebarContent>
-        <CDBSidebarFooter
-          className="m-1"
-          style={{
-            textAlign: "center",
-          }}
-        >
-          Navy Web Player
-        </CDBSidebarFooter>
-      </CDBSidebar>
-    </div>
-  );
-};
+import {
+  CDBSidebar,
+  CDBSidebarContent,
+  CDBSidebarFooter,
+  CDBSidebarHeader,
+  CDBSidebarMenu,
+  CDBSidebarMenuItem,
+} from "cdbreact";
+import { NavLink } from "react-router-dom";
+import { Logo } from "./Logo";
+
+export const Sidebar = () => {
+  return (
+    <div
+      style={{
+        display: "flex",
+        height: "100vh",
+        overflow: "scroll initial",
+        top: 0,
+        position: "fixed",
+      }}
+    >
+      <CDBSidebar backgroundColor="#212529">
+        <CDBSidebarHeader prefix={<i className="fa fa-bars fa-large"></i>}>
+          <Logo />
+        </CDBSidebarHeader>
+        <CDBSidebarContent className="sidebar-content">
+          <CDBSidebarMenu>
+            <NavLink exact to="/" activeClassName="activeClicked">
+              <CDBSidebarMenuItem icon="columns">Главная</CDBSidebarMenuItem>
+            </NavLink>
+            <NavLink exact to="/search" activeClassName="activeClicked">
+              <CDBSidebarMenuItem icon="search">Поиск</CDBSidebarMenuItem>
+            </NavLink>
+            <NavLink exact to="/library" activeClassName="activeClicked">
+              <CDBSidebarMenuItem icon="user">Моя музыка</CDBSidebarMenuItem>
+            </NavLink>
+          </CDBSidebarMenu>
+        </CDBSidebarContent>
+        <CDBSidebarFooter
+          className="m-1"
+          style={{
+            textAlign: "center",
+          }}
+        >
+          Navy Web Player
+        </CDBSidebarFooter>
+      </CDBSidebar>
+    </div>
+  );
+};

+ 45 - 45
src/helpers/index.js

@@ -1,45 +1,45 @@
-export const jwtDecode = (token) => {
-  try {
-    let arrToken = token.split(".");
-    let base64Token = atob(arrToken[1]);
-    return JSON.parse(base64Token);
-  } catch (e) {
-    console.log("Error JWT: " + e);
-  }
-};
-
-export const getGQL =
-  (url) =>
-  async (query, variables = {}) => {
-    let obj = await fetch(url, {
-      method: "POST",
-      headers: {
-        "Content-Type": "application/json",
-        Authorization: localStorage.authToken
-          ? "Bearer " + localStorage.authToken
-          : {},
-      },
-      body: JSON.stringify({ query, variables }),
-    });
-    let a = await obj.json();
-    if (!a.data && a.errors) throw new Error(JSON.stringify(a.errors));
-    return a.data[Object.keys(a.data)[0]];
-  };
-
-export const backURL = "http://player.asmer.fs.a-level.com.ua";
-
-export const gql = getGQL(backURL + "/graphql");
-
-export function validateEmail(email) {
-  return /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(
-    email.toLowerCase()
-  );
-}
-
-export function validatePassword(password) {
-  return /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{6,}/.test(password);
-}
-
-export function validateNickname(nick) {
-  return /[^a-zA-Z0-9]+/g.test(nick);
-}
+export const jwtDecode = (token) => {
+  try {
+    let arrToken = token.split(".");
+    let base64Token = atob(arrToken[1]);
+    return JSON.parse(base64Token);
+  } catch (e) {
+    console.log("Error JWT: " + e);
+  }
+};
+
+export const getGQL =
+  (url) =>
+  async (query, variables = {}) => {
+    let obj = await fetch(url, {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        Authorization: localStorage.authToken
+          ? "Bearer " + localStorage.authToken
+          : {},
+      },
+      body: JSON.stringify({ query, variables }),
+    });
+    let a = await obj.json();
+    if (!a.data && a.errors) throw new Error(JSON.stringify(a.errors));
+    return a.data[Object.keys(a.data)[0]];
+  };
+
+export const backURL = "http://player.asmer.fs.a-level.com.ua";
+
+export const gql = getGQL(backURL + "/graphql");
+
+export function validateEmail(email) {
+  return /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(
+    email.toLowerCase()
+  );
+}
+
+export function validatePassword(password) {
+  return /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{6,}/.test(password);
+}
+
+export function validateNickname(nick) {
+  return /^[a-z0-9_-]{3,8}$/.test(nick);
+}

+ 4 - 0
src/images/no-view.svg

@@ -0,0 +1,4 @@
+<!-- License: Apache. Made by grommet: https://github.com/grommet/grommet-icons -->
+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+  <path fill="none" stroke="#000" stroke-width="2" d="M3,12 L6,12 C6.5,14.5 9.27272727,17 12,17 C14.7272727,17 17.5,14.5 18,12 L21,12 M12,17 L12,20 M7.5,15.5 L5.5,17.5 M16.5,15.5 L18.5,17.5"/>
+</svg>

+ 4 - 0
src/images/view.svg

@@ -0,0 +1,4 @@
+<!-- License: Apache. Made by grommet: https://github.com/grommet/grommet-icons -->
+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+  <path fill="none" stroke="#000" stroke-width="2" d="M12,17 C9.27272727,17 6,14.2222222 6,12 C6,9.77777778 9.27272727,7 12,7 C14.7272727,7 18,9.77777778 18,12 C18,14.2222222 14.7272727,17 12,17 Z M11,12 C11,12.55225 11.44775,13 12,13 C12.55225,13 13,12.55225 13,12 C13,11.44775 12.55225,11 12,11 C11.44775,11 11,11.44775 11,12 Z"/>
+</svg>

+ 78 - 79
src/pages/Auth.js

@@ -1,79 +1,78 @@
-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 { backURL } from "../helpers";
-
-const Auth = ({ auth, promise, actionLogOut }) => {
-  if (
-    auth.token &&
-    (history.location.pathname === "/login" ||
-      history.location.pathname === "/signup")
-  ) {
-    history.push("/");
-  }
-  if (auth.token) {
-    localStorage.authToken = auth.token;
-  }
-  return (
-    <>
-      {auth.payload ? (
-        <NavDropdown
-          id="dropdownProfileMenu"
-          title={
-            <div className="pull-left d-inline-block">
-              <img
-                className="thumbnail-image avatarHeader"
-                src={
-                  promise?.user?.payload?.avatar
-                    ? `${backURL}${promise.user.payload.avatar.url}`
-                    : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
-                }
-                alt="Avatar"
-              />
-
-              {promise?.user?.payload?.nick}
-            </div>
-          }
-          menuVariant="dark"
-        >
-          <NavDropdown.Item
-            componentclass={Link}
-            href="/profile"
-            to={`/profile`}
-          >
-            Профиль
-          </NavDropdown.Item>
-          <NavDropdown.Item
-            componentclass={Link}
-            href="/settings"
-            to={`/settings`}
-          >
-            Настройки
-          </NavDropdown.Item>
-          <NavDropdown.Divider />
-          <NavDropdown.Item as="button" onClick={() => actionLogOut()}>
-            Выйти
-          </NavDropdown.Item>
-        </NavDropdown>
-      ) : (
-        <>
-          <Link className="nav-link" to={"/signup"}>
-            Зарегистрироваться
-          </Link>
-          <Link className="btn btn-light" to={"/login"}>
-            Войти
-          </Link>
-        </>
-      )}
-    </>
-  );
-};
-
-export const CAuth = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
-  {
-    actionLogOut: actionAuthLogout,
-  }
-)(Auth);
+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 { backURL } from "../helpers";
+
+const Auth = ({ auth, promise, actionLogOut }) => {
+  if (
+    auth.token &&
+    (history.location.pathname === "/login" ||
+      history.location.pathname === "/signup")
+  ) {
+    history.push("/");
+  }
+  if (auth.token) {
+    localStorage.authToken = auth.token;
+  }
+  return (
+    <>
+      {auth.payload ? (
+        <NavDropdown
+          id="dropdownProfileMenu"
+          title={
+            <div className="pull-left d-inline-block">
+              <img
+                className="thumbnail-image avatarHeader"
+                src={
+                  promise?.user?.payload?.avatar
+                    ? `${backURL}/${promise.user.payload.avatar.url}`
+                    : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
+                }
+                alt="Avatar"
+              />
+              {promise?.user?.payload?.nick}
+            </div>
+          }
+          menuVariant="dark"
+        >
+          <NavDropdown.Item
+            componentclass={Link}
+            href="/profile"
+            to={`/profile`}
+          >
+            Профиль
+          </NavDropdown.Item>
+          <NavDropdown.Item
+            componentclass={Link}
+            href="/settings"
+            to={`/settings`}
+          >
+            Настройки
+          </NavDropdown.Item>
+          <NavDropdown.Divider />
+          <NavDropdown.Item as="button" onClick={() => actionLogOut()}>
+            Выйти
+          </NavDropdown.Item>
+        </NavDropdown>
+      ) : (
+        <>
+          <Link className="nav-link" to={"/signup"}>
+            Зарегистрироваться
+          </Link>
+          <Link className="btn btn-light" to={"/login"}>
+            Войти
+          </Link>
+        </>
+      )}
+    </>
+  );
+};
+
+export const CAuth = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    actionLogOut: actionAuthLogout,
+  }
+)(Auth);

+ 30 - 35
src/pages/Library.js

@@ -1,35 +1,30 @@
-import { connect } from "react-redux";
-import { AuthCheck } from "./../components/AuthCheck";
-import { history } from "./../App";
-import { actionFindTracks, actionFindUser } from "./../actions/index";
-import { Button } from "react-bootstrap";
-import { CMyDropzone } from "../components/Dropzone";
-
-const Library = ({ auth, promise, actionTracks, actionUser }) => {
-  return (
-    <div className="SearchPage">
-      {auth.token && history.location.pathname === "/library" ? (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <h1>Ваша библиотека с музыкой, {promise?.user?.payload?.nick}</h1>
-          <CMyDropzone />
-          <Button onClick={() => actionTracks()}>Tracks</Button>
-          <Button onClick={() => actionUser("61d45a16e9472933a6785f04")}>
-            Me
-          </Button>
-        </div>
-      ) : (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <AuthCheck header="Ваша музыка" />
-        </div>
-      )}
-    </div>
-  );
-};
-
-export const CLibrary = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
-  {
-    actionTracks: actionFindTracks,
-    actionUser: actionFindUser,
-  }
-)(Library);
+import { connect } from "react-redux";
+import { AuthCheck } from "./../components/AuthCheck";
+import { history } from "./../App";
+import { actionFindTracks, actionFindUser } from "./../actions/index";
+import { CMyDropzone } from "../components/Dropzone";
+
+const Library = ({ auth, promise, actionTracks, actionUser }) => {
+  return (
+    <div className="SearchPage">
+      {auth.token && history.location.pathname === "/library" ? (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <h1>Ваша библиотека с музыкой, {promise?.user?.payload?.nick}</h1>
+          <CMyDropzone />
+        </div>
+      ) : (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <AuthCheck header="Ваша музыка" />
+        </div>
+      )}
+    </div>
+  );
+};
+
+export const CLibrary = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    actionTracks: actionFindTracks,
+    actionUser: actionFindUser,
+  }
+)(Library);

+ 73 - 73
src/pages/Login.js

@@ -1,73 +1,73 @@
-import { useState } from "react";
-import { actionFullLogin } from "./../actions/index";
-import { Form, Row, Col, Button, Alert } from "react-bootstrap";
-import { connect } from "react-redux";
-import { Link } from "react-router-dom";
-
-const LoginForm = ({ promise, onLogin }) => {
-  const [login, setLogin] = useState("");
-  const [password, setPassword] = useState("");
-  return (
-    <div className="AuthForm mx-auto mt-5">
-      <Form>
-        {promise.login &&
-        promise.login.status === "RESOLVED" &&
-        !promise.login.payload ? (
-          <Alert>
-            Извините, но такого пользователя не существует, попробуйте
-            зарегистрироваться или повторите ещё раз.
-          </Alert>
-        ) : null}
-        <Form.Group as={Row} className="mb-3" controlId="formHorizontalEmail">
-          <Form.Label column sm={2}>
-            Почта:
-          </Form.Label>
-          <Col sm={10}>
-            <Form.Control
-              type="text"
-              placeholder="Введите вашу почту"
-              onChange={(e) => setLogin(e.target.value)}
-            />
-          </Col>
-        </Form.Group>
-        <Form.Group
-          as={Row}
-          className="mb-3"
-          controlId="formHorizontalPassword"
-        >
-          <Form.Label column sm={2}>
-            Пароль:
-          </Form.Label>
-          <Col sm={10}>
-            <Form.Control
-              type="password"
-              placeholder="Введите ваш пароль"
-              onChange={(e) => setPassword(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={login.length < 1 || password.length < 1 ? true : false}
-              onClick={() => onLogin(login, password)}
-            >
-              Войти
-            </Button>
-          </Col>
-          <Col sm={{ span: 10, offset: 2 }}>
-            Нет аккаунта?{" "}
-            <Link className="btn btn-warning" to={"/signup"}>
-              Зарегистрироваться
-            </Link>
-          </Col>
-        </Form.Group>
-      </Form>
-    </div>
-  );
-};
-
-export const CLoginForm = connect((state) => ({ promise: state.promise }), {
-  onLogin: actionFullLogin,
-})(LoginForm);
+import { useState } from "react";
+import { actionFullLogin } from "./../actions/index";
+import { Form, Row, Col, Button, Alert } from "react-bootstrap";
+import { connect } from "react-redux";
+import { Link } from "react-router-dom";
+
+const LoginForm = ({ promise, onLogin }) => {
+  const [login, setLogin] = useState("");
+  const [password, setPassword] = useState("");
+  return (
+    <div className="AuthForm mx-auto mt-5">
+      <Form>
+        {promise.login &&
+        promise.login.status === "RESOLVED" &&
+        !promise.login.payload ? (
+          <Alert>
+            Извините, но такого пользователя не существует, попробуйте
+            зарегистрироваться или повторите ещё раз.
+          </Alert>
+        ) : null}
+        <Form.Group as={Row} className="mb-3" controlId="formHorizontalEmail">
+          <Form.Label column sm={2}>
+            Почта:
+          </Form.Label>
+          <Col sm={10}>
+            <Form.Control
+              type="text"
+              placeholder="Введите вашу почту"
+              onChange={(e) => setLogin(e.target.value)}
+            />
+          </Col>
+        </Form.Group>
+        <Form.Group
+          as={Row}
+          className="mb-3"
+          controlId="formHorizontalPassword"
+        >
+          <Form.Label column sm={2}>
+            Пароль:
+          </Form.Label>
+          <Col sm={10}>
+            <Form.Control
+              type="password"
+              placeholder="Введите ваш пароль"
+              onChange={(e) => setPassword(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={login.length < 1 || password.length < 1 ? true : false}
+              onClick={() => onLogin(login, password)}
+            >
+              Войти
+            </Button>
+          </Col>
+          <Col sm={{ span: 10, offset: 2 }}>
+            Нет аккаунта?{" "}
+            <Link className="btn btn-warning" to={"/signup"}>
+              Зарегистрироваться
+            </Link>
+          </Col>
+        </Form.Group>
+      </Form>
+    </div>
+  );
+};
+
+export const CLoginForm = connect((state) => ({ promise: state.promise }), {
+  onLogin: actionFullLogin,
+})(LoginForm);

+ 24 - 24
src/pages/Page404.js

@@ -1,24 +1,24 @@
-import { Link } from "react-router-dom";
-
-export const Page404 = () => (
-  <div className="text-center">
-    <h1>404 Error Page</h1>
-    <p className="zoom-area">Page was not found</p>
-    <section className="error-container">
-      <span className="four">
-        <span className="screen-reader-text">4</span>
-      </span>
-      <span className="zero">
-        <span className="screen-reader-text">0</span>
-      </span>
-      <span className="four">
-        <span className="screen-reader-text">4</span>
-      </span>
-    </section>
-    <div className="link-container">
-      <Link to="/" className="btn btn-light">
-        Go to home
-      </Link>
-    </div>
-  </div>
-);
+import { Link } from "react-router-dom";
+
+export const Page404 = () => (
+  <div className="text-center">
+    <h1>404 Error Page</h1>
+    <p className="zoom-area">Page was not found</p>
+    <section className="error-container">
+      <span className="four">
+        <span className="screen-reader-text">4</span>
+      </span>
+      <span className="zero">
+        <span className="screen-reader-text">0</span>
+      </span>
+      <span className="four">
+        <span className="screen-reader-text">4</span>
+      </span>
+    </section>
+    <div className="link-container">
+      <Link to="/" className="btn btn-light">
+        Go to home
+      </Link>
+    </div>
+  </div>
+);

+ 318 - 233
src/pages/Profile.js

@@ -1,233 +1,318 @@
-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 {
-  validateEmail,
-  validatePassword,
-  validateNickname,
-} from "./../helpers/index";
-import { actionUserUpdate } from "./../actions/index";
-import { Loader } from "../components/Loader";
-import { CMyDropzone } from "../components/Dropzone";
-import { Spoiler } from "../components/Spoiler";
-
-const Profile = ({ auth, promise, actionUpdate }) => {
-  // promise.user?.payload?.login
-  const [login, setLogin] = useState("");
-  // promise.user?.payload?.nick
-  const [nick, setNickname] = useState("");
-  const [password, setPassword] = useState("");
-  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}</h1>
-          <Spoiler
-            children={
-              <>
-                <br />
-                <form
-                  action="/upload"
-                  method="post"
-                  enctype="multipart/form-data"
-                  id="form"
-                >
-                  <img
-                    className="avatarProfile"
-                    src={
-                      promise?.user?.payload?.avatar
-                        ? "https://dthezntil550i.cloudfront.net/kg/latest/kg1802132010216500004834729/1280_960/557d644f-12f3-49e1-bb66-23c16400540d.png"
-                        : "https://i.ibb.co/bBxzmTm/default-avatar.jpg"
-                    }
-                    alt="Avatar"
-                  />
-                  <CMyDropzone />
-                </form>
-              </>
-            }
-            header={<h3>Изменить аватар</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  {/*
-                {validateNickname(nick) ? null : (
-                  <Alert>
-                    Никнейм может состоять только из букв и цифр, а так же иметь
-                    максимальную длину в 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={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={
-                          validateEmail(login) &&
-                          password.length !== 0 &&
-                          validatePassword(password) &&
-                          validateNickname(nick)
-                            ? false
-                            : true
-                        }
-                        onClick={() =>
-                          actionUpdate(promise?.user?.payload?._id)
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить логин</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  {/* {validateEmail(login) ? null : (
-                  <Alert>Email должен быть в формате: email@gmail.com.</Alert>
-                )}
-                {validatePassword(password) ? null : (
-                  <Alert>
-                    Пароль должен быть от 6 символов, иметь хотя бы одну цифру и
-                    заглавную букву.
-                  </Alert>
-                )}
-                {validateNickname(nick) ? null : (
-                  <Alert>
-                    Никнейм может состоять только из букв и цифр, а так же иметь
-                    максимальную длину в 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={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) &&
-                          password.length !== 0 &&
-                          validatePassword(password) &&
-                          validateNickname(nick)
-                            ? false
-                            : true
-                        }
-                        onClick={() =>
-                          actionUpdate(promise?.user?.payload?._id)
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить почту</h3>}
-          />
-          <Spoiler
-            children={
-              <>
-                <br />
-                <Form>
-                  <Form.Group
-                    as={Row}
-                    className="m-2"
-                    controlId="formHorizontalPassword"
-                  >
-                    <Form.Label column sm={2}>
-                      Пароль:
-                    </Form.Label>
-                    <Col sm={10}>
-                      <Form.Control
-                        type="password"
-                        placeholder="Введите ваш новый пароль"
-                        onChange={(e) => setPassword(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) &&
-                          password.length !== 0 &&
-                          validatePassword(password) &&
-                          validateNickname(nick)
-                            ? false
-                            : true
-                        }
-                        onClick={() =>
-                          actionUpdate(promise?.user?.payload?._id)
-                        }
-                      >
-                        Сохранить
-                      </Button>
-                    </Col>
-                  </Form.Group>
-                </Form>
-              </>
-            }
-            header={<h3>Изменить пароль</h3>}
-          />
-        </div>
-      ) : (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <AuthCheck header="Ваш профиль" />
-        </div>
-      )}
-    </div>
-  );
-};
-
-export const CProfile = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
-  {
-    actionUpdate: actionUserUpdate,
-  }
-)(Profile);
+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 {
+  backURL,
+  validateEmail,
+  validatePassword,
+  validateNickname,
+} from "./../helpers/index";
+import {
+  actionUserUpdate,
+  actionAboutMe,
+  actionChangePassword,
+} from "./../actions/index";
+import { Loader } from "../components/Loader";
+import { CMyDropzone } from "../components/Dropzone";
+import { Spoiler } from "../components/Spoiler";
+
+const Profile = ({ auth, promise, actionUpdate, changePassword, aboutMe }) => {
+  const [login, setLogin] = useState("");
+  const [nick, setNickname] = useState("");
+  const [password, setPassword] = useState("");
+  const [newPassword, setNewPassword] = useState("");
+  const [passwordShown, setPasswordShown] = useState(false);
+
+  const togglePassword = () => {
+    setPasswordShown(!passwordShown);
+  };
+
+  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={() => {
+                          actionUpdate({
+                            _id: promise?.user?.payload?._id,
+                            nick,
+                          });
+                          aboutMe();
+                        }}
+                      >
+                        Сохранить
+                      </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={() => {
+                          actionUpdate({
+                            _id: promise?.user?.payload?._id,
+                            login,
+                          });
+                          aboutMe();
+                        }}
+                      >
+                        Сохранить
+                      </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={togglePassword}
+                      >
+                        {`${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
+                          );
+                          aboutMe();
+                        }}
+                      >
+                        Сохранить
+                      </Button>
+                    </Col>
+                  </Form.Group>
+                </Form>
+              </>
+            }
+            header={<h3>Изменить пароль</h3>}
+          />
+        </div>
+      ) : (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <AuthCheck header="Ваш профиль" />
+        </div>
+      )}
+    </div>
+  );
+};
+
+export const CProfile = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    actionUpdate: actionUserUpdate,
+    changePassword: actionChangePassword,
+    aboutMe: actionAboutMe,
+  }
+)(Profile);

+ 94 - 94
src/pages/Register.js

@@ -1,94 +1,94 @@
-import { useState } from "react";
-import { actionFullRegister } from "./../actions/index";
-import { Form, Row, Col, Button, Alert } from "react-bootstrap";
-import { connect } from "react-redux";
-import { Link } from "react-router-dom";
-import { validateEmail, validatePassword } from "./../helpers/index";
-
-const RegisterForm = ({ promise, auth, onRegister }) => {
-  const [login, setLogin] = useState("");
-  const [password, setPassword] = useState("");
-
-  return (
-    <div className="AuthForm mx-auto mt-5">
-      <Form>
-        {login.length === 0 ? null : validateEmail(login) ? (
-          password.length === 0 ? null : validatePassword(password) ? null : (
-            <Alert>
-              Пароль должен быть от 6 символов, иметь хотя бы одну цифру и
-              заглавную букву.
-            </Alert>
-          )
-        ) : (
-          <Alert>Email должен быть в формате: email@gmail.com.</Alert>
-        )}
-        {Object.keys(auth).length === 0 &&
-        promise?.registration?.status === "RESOLVED" ? (
-          <Alert>
-            Произошла ошибка при регистрации, пожалуйста, повторите ещё раз.
-            Возможно, такой пользователь уже существует.
-          </Alert>
-        ) : null}
-        <Form.Group as={Row} className="mb-3" controlId="formHorizontalEmail">
-          <Form.Label column sm={2}>
-            Почта:
-          </Form.Label>
-          <Col sm={10}>
-            <Form.Control
-              type="email"
-              required
-              placeholder="Введите вашу почту"
-              onChange={(e) => setLogin(e.target.value)}
-            />
-          </Col>
-        </Form.Group>
-        <Form.Group
-          as={Row}
-          className="mb-3"
-          controlId="formHorizontalPassword"
-        >
-          <Form.Label column sm={2}>
-            Пароль:
-          </Form.Label>
-          <Col sm={10}>
-            <Form.Control
-              type="password"
-              required
-              placeholder="Введите ваш пароль"
-              onChange={(e) => setPassword(e.target.value)}
-            />
-          </Col>
-        </Form.Group>
-        <Form.Group as={Row} className="mb-3">
-          <Col sm={{ span: 10, offset: 2 }} className="my-3">
-            <Button
-              id="signupBtn"
-              variant="success"
-              disabled={
-                validateEmail(login) && validatePassword(password)
-                  ? false
-                  : true
-              }
-              onClick={() => onRegister(login, password)}
-            >
-              Зарегистрироваться
-            </Button>
-          </Col>
-          <Col sm={{ span: 10, offset: 2 }}>
-            Есть аккаунт?{" "}
-            <Link className="btn btn-warning" to={"/login"}>
-              Авторизоваться
-            </Link>
-          </Col>
-        </Form.Group>
-      </Form>
-    </div>
-  );
-};
-
-export const CSignUpForm = connect(
-  (state) => ({ auth: state.auth, promise: state.promise }),
-  {
-    onRegister: actionFullRegister,
-  }
-)(RegisterForm);
+import { useState } from "react";
+import { actionFullRegister } from "./../actions/index";
+import { Form, Row, Col, Button, Alert } from "react-bootstrap";
+import { connect } from "react-redux";
+import { Link } from "react-router-dom";
+import { validateEmail, validatePassword } from "./../helpers/index";
+
+const RegisterForm = ({ promise, auth, onRegister }) => {
+  const [login, setLogin] = useState("");
+  const [password, setPassword] = useState("");
+
+  return (
+    <div className="AuthForm mx-auto mt-5">
+      <Form>
+        {login.length === 0 ? null : validateEmail(login) ? (
+          password.length === 0 ? null : validatePassword(password) ? null : (
+            <Alert>
+              Пароль должен быть от 6 символов, иметь хотя бы одну цифру и
+              заглавную букву.
+            </Alert>
+          )
+        ) : (
+          <Alert>Email должен быть в формате: email@gmail.com.</Alert>
+        )}
+        {Object.keys(auth).length === 0 &&
+        promise?.registration?.status === "RESOLVED" ? (
+          <Alert>
+            Произошла ошибка при регистрации, пожалуйста, повторите ещё раз.
+            Возможно, такой пользователь уже существует.
+          </Alert>
+        ) : null}
+        <Form.Group as={Row} className="mb-3" controlId="formHorizontalEmail">
+          <Form.Label column sm={2}>
+            Почта:
+          </Form.Label>
+          <Col sm={10}>
+            <Form.Control
+              type="email"
+              required
+              placeholder="Введите вашу почту"
+              onChange={(e) => setLogin(e.target.value)}
+            />
+          </Col>
+        </Form.Group>
+        <Form.Group
+          as={Row}
+          className="mb-3"
+          controlId="formHorizontalPassword"
+        >
+          <Form.Label column sm={2}>
+            Пароль:
+          </Form.Label>
+          <Col sm={10}>
+            <Form.Control
+              type="password"
+              required
+              placeholder="Введите ваш пароль"
+              onChange={(e) => setPassword(e.target.value)}
+            />
+          </Col>
+        </Form.Group>
+        <Form.Group as={Row} className="mb-3">
+          <Col sm={{ span: 10, offset: 2 }} className="my-3">
+            <Button
+              id="signupBtn"
+              variant="success"
+              disabled={
+                validateEmail(login) && validatePassword(password)
+                  ? false
+                  : true
+              }
+              onClick={() => onRegister(login, password)}
+            >
+              Зарегистрироваться
+            </Button>
+          </Col>
+          <Col sm={{ span: 10, offset: 2 }}>
+            Есть аккаунт?{" "}
+            <Link className="btn btn-warning" to={"/login"}>
+              Авторизоваться
+            </Link>
+          </Col>
+        </Form.Group>
+      </Form>
+    </div>
+  );
+};
+
+export const CSignUpForm = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    onRegister: actionFullRegister,
+  }
+)(RegisterForm);

+ 33 - 23
src/pages/Search.js

@@ -1,23 +1,33 @@
-import { connect } from "react-redux";
-import { SearchField } from "./../components/SearchField";
-import { AuthCheck } from "./../components/AuthCheck";
-import { history } from "./../App";
-
-const Search = ({ auth }) => {
-  return (
-    <div className="SearchPage">
-      {auth.token && history.location.pathname === "/search" ? (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <h1>Поиск по сайту</h1>
-          <SearchField />
-        </div>
-      ) : (
-        <div className="d-block mx-auto mt-2 container w-50">
-          <AuthCheck header="Поиск по сайту" />
-        </div>
-      )}
-    </div>
-  );
-};
-
-export const CSearch = connect((state) => ({ auth: state.auth }), null)(Search);
+import { connect } from "react-redux";
+import { SearchField } from "./../components/SearchField";
+import { AuthCheck } from "./../components/AuthCheck";
+import { history } from "./../App";
+import { Button } from "react-bootstrap";
+import { actionFindTracks, actionFindUser } from "./../actions/index";
+import { backURL } from "./../helpers/index";
+
+const Search = ({ auth, actionTracks, actionUser }) => {
+  return (
+    <div className="SearchPage">
+      {auth.token && history.location.pathname === "/search" ? (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <h1>Поиск по сайту</h1>
+          <SearchField />
+          <Button onClick={() => actionTracks()}>Tracks</Button>
+        </div>
+      ) : (
+        <div className="d-block mx-auto mt-2 container w-50">
+          <AuthCheck header="Поиск по сайту" />
+        </div>
+      )}
+    </div>
+  );
+};
+
+export const CSearch = connect(
+  (state) => ({ auth: state.auth, promise: state.promise }),
+  {
+    actionTracks: actionFindTracks,
+    actionUser: actionFindUser,
+  }
+)(Search);

+ 39 - 39
src/reducers/index.js

@@ -1,39 +1,39 @@
-import { jwtDecode } from "./../helpers/index";
-
-export function promiseReducer(
-  state = {},
-  { type, status, payload, error, name }
-) {
-  if (type === "PROMISE") {
-    return {
-      ...state,
-      [name]: { status, payload, error },
-    };
-  }
-  return state;
-}
-
-export function authReducer(state, { type, token }) {
-  if (!state) {
-    if (localStorage.authToken) {
-      type = "AUTH_LOGIN";
-      token = localStorage.authToken;
-    } else state = {};
-  }
-  if (type === "AUTH_LOGIN") {
-    let payload = jwtDecode(token);
-    if (!!token && typeof payload === "object") {
-      localStorage.authToken = token;
-      return {
-        ...state,
-        token,
-        payload,
-      };
-    } else return state;
-  }
-  if (type === "AUTH_LOGOUT") {
-    localStorage.removeItem("authToken");
-    return {};
-  }
-  return state;
-}
+import { jwtDecode } from "./../helpers/index";
+
+export function promiseReducer(
+  state = {},
+  { type, status, payload, error, name }
+) {
+  if (type === "PROMISE") {
+    return {
+      ...state,
+      [name]: { status, payload, error },
+    };
+  }
+  return state;
+}
+
+export function authReducer(state, { type, token }) {
+  if (!state) {
+    if (localStorage.authToken) {
+      type = "AUTH_LOGIN";
+      token = localStorage.authToken;
+    } else state = {};
+  }
+  if (type === "AUTH_LOGIN") {
+    let payload = jwtDecode(token);
+    if (!!token && typeof payload === "object") {
+      localStorage.authToken = token;
+      return {
+        ...state,
+        token,
+        payload,
+      };
+    } else return state;
+  }
+  if (type === "AUTH_LOGOUT") {
+    localStorage.removeItem("authToken");
+    return {};
+  }
+  return state;
+}