浏览代码

finished change password

yankevych0210 1 年之前
父节点
当前提交
9b31bd21f3

+ 8 - 0
.prettierrc

@@ -0,0 +1,8 @@
+{
+  "printWidth": 80,
+  "tabWidth": 2,
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "jsxBracketSameLine": false,
+  "parser": "babel"
+}

+ 29 - 10
src/App.js

@@ -6,16 +6,10 @@ import { YourWorksPage } from './pages/YourWorksPage/YourWorksPage';
 import { LoginPage } from './pages/LoginPage/LoginPage';
 import { SignUpPage } from './pages/SignUpPage/SignUpPage';
 import { SettingsPage } from './pages/SettingsPage/SettingsPage';
+import { PrivateRoute } from './layouts/PrivateRoute';
 
 //  TODO:
 //  1. icons
-//  2. console
-//  3. search
-//  4. sort
-//  5. deleted
-//  6. aliases
-//  7. extraKeys for Editor
-//  8. dropFile
 
 function App() {
   return (
@@ -24,9 +18,34 @@ function App() {
       <Route path="/pen" element={<PenPage />} />
       <Route path="/login" element={<LoginPage />} />
       <Route path="/signup" element={<SignUpPage />} />
-      <Route path="/settings" element={<SettingsPage />} />
-      <Route path="/your-works" element={<YourWorksPage />} />
-      <Route path="/your-works/:id" element={<PenPage />} />
+
+      <Route
+        path="/settings"
+        element={
+          <PrivateRoute>
+            <SettingsPage />
+          </PrivateRoute>
+        }
+      />
+
+      <Route
+        path="/your-works"
+        element={
+          <PrivateRoute>
+            <YourWorksPage />
+          </PrivateRoute>
+        }
+      />
+
+      <Route
+        path="/your-works/:id"
+        element={
+          <PrivateRoute>
+            <PenPage />
+          </PrivateRoute>
+        }
+      />
+
       <Route path="*" element={<ErrorPage />} />
     </Routes>
   );

+ 26 - 18
src/components/Console/Console.jsx

@@ -3,6 +3,7 @@ import { useConsole } from '../../hooks/useConsole';
 import { useEffect } from 'react';
 import { useSelector } from 'react-redux';
 import ConsoleItem from '../ConsoleItem/ConsoleItem';
+import { Bar, Section } from 'react-simple-resizer';
 
 export function Console({ isOpen, close }) {
   const appConsole = useConsole();
@@ -24,27 +25,34 @@ export function Console({ isOpen, close }) {
 
   if (isOpen) {
     return (
-      <div className={style.console}>
-        <div className={style.header}>
+      <>
+        <Bar className={style.header}>
           <h2>Console</h2>
           <button onClick={() => window.console.clear()}>Clear</button>
           <button onClick={close}>x</button>
-        </div>
-        <div className={style.out}>
-          {appConsole.output.map((item, index) => (
-            <ConsoleItem key={index} message={item} />
-          ))}
-        </div>
-        <div className={style.entryField}>
-          <span>&#62;</span>
-          <input
-            type="text"
-            value={appConsole.command}
-            onChange={appConsole.handleCommandChange}
-            onKeyDown={appConsole.handleKeyDown}
-          />
-        </div>
-      </div>
+        </Bar>
+
+        <Section
+          children={
+            <div className={style.console}>
+              <div className={style.out}>
+                {appConsole.output.map((item, index) => (
+                  <ConsoleItem key={index} message={item} />
+                ))}
+              </div>
+              <div className={style.entryField}>
+                <span>&#62;</span>
+                <input
+                  type="text"
+                  value={appConsole.command}
+                  onChange={appConsole.handleCommandChange}
+                  onKeyDown={appConsole.handleKeyDown}
+                />
+              </div>
+            </div>
+          }
+        />
+      </>
     );
   } else {
     return null;

+ 52 - 41
src/components/Console/Console.module.scss

@@ -3,45 +3,24 @@
 .console {
   display: flex;
   flex-direction: column;
-  height: 20%;
+  height: 100%;
 
-  .header {
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-    background-color: #060606;
-    color: #aaaebd;
-    padding: 7px 10px;
-
-    h2 {
-      font-weight: 700;
-      font-size: 1.1rem;
-      margin-right: auto;
-    }
-
-    button {
-      @extend %buttonGrey;
-      border-radius: 3px;
-      padding: 5px 8px;
-    }
-
-    button:last-of-type {
-      margin-left: 5px;
-    }
-  }
-
-  .out {
+  .entryField {
     display: flex;
-    flex-direction: column;
-    flex: 1;
-    background-color: #1d1e22;
+    text-align: center;
+    background-color: #343539;
+    height: 30px;
     color: white;
-    overflow: scroll;
+  
+    & span {
+      padding: 5px 0 5px 10px;
+    }
   }
-
+  
   // FIXME: height
   input {
     color: white;
+    width: 100%;
     height: auto;
     padding: 7px;
     font-size: 15px;
@@ -51,15 +30,47 @@
     resize: none;
     outline: none;
   }
-  .entryField {
-    display: flex;
-    text-align: center;
-    background-color: #343539;
-    height: 30px;
-    color: white;
+ 
+}
 
-    & span {
-      padding: 5px 0 5px 10px;
-    }
+.out {
+  display: flex;
+  flex-direction: column;
+  
+  flex: 1 ;
+  height: 100% ;
+  background-color: #1d1e22;
+  color: white;
+  overflow: scroll;
+ 
+}
+
+.header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  background-color: #060606;
+  color: #aaaebd;
+  padding: 7px 10px;
+
+  h2 {
+    font-weight: 700;
+    font-size: 1.1rem;
+    margin-right: auto;
+  }
+
+  button {
+    @extend %buttonGrey;
+    border-radius: 3px;
+    padding: 5px 8px;
+    z-index: 2;
+  }
+
+  button:last-of-type {
+    margin-left: 5px;
   }
 }
+
+
+
+

+ 2 - 2
src/components/ConsoleItem/ConsoleItem.module.scss

@@ -1,8 +1,8 @@
 @import '../../scss/index.scss';
 
 .consoleItem {
-    padding: 10px 10px 5px;
-    border-bottom: 1px solid #5A5F73;
+    padding: 8px 10px 8px;
+    border-bottom: 1px solid #353841;
     font-size: 17px;
     font-weight: 500;
 }

+ 5 - 7
src/components/Header/Header.jsx

@@ -5,14 +5,14 @@ import { useDispatch, useSelector } from 'react-redux';
 import { fetchUser } from '../../store/user/actions/fetchUser';
 import { useEffect } from 'react';
 import { getUserIdFromJwt } from '../../utils/getUserIdFromJwt';
-import userInitImage from '../../assets/img/initialUserImage.jpeg';
 import { usePopup } from '../../hooks/usePopup';
 import { UserPopup } from '../UserPopup/UserPopup';
+import { LoadingPage } from '../../pages/LoadingPage/LoadingPage';
 
 export const Header = () => {
   const dispatch = useDispatch();
   const { isAuth } = useSelector((state) => state.auth);
-  const { avatar } = useSelector((state) => state.user);
+  const { login, avatar, isLoading } = useSelector((state) => state.user);
   const userPopup = usePopup();
 
   useEffect(() => {
@@ -23,6 +23,7 @@ export const Header = () => {
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
 
+  if (isLoading) return <LoadingPage />;
   return (
     <header className={style.header}>
       <NavLink className={style.logoBlock} to="/">
@@ -31,11 +32,8 @@ export const Header = () => {
 
       {isAuth ? (
         <nav>
-          <img
-            src={avatar ? avatar : userInitImage}
-            onClick={userPopup.open}
-            alt="userImage"
-          />
+          <span>Hello, {login}!</span>
+          <img src={avatar} onClick={userPopup.open} alt="userImage" />
           <UserPopup
             popupRef={userPopup.ref}
             isOpen={userPopup.isPopupVisible}

+ 11 - 0
src/components/Header/Header.module.scss

@@ -27,6 +27,17 @@
 
 
   nav {
+    display: flex;
+    align-items: flex-end;
+    
+    span {
+      font-size: 20px;
+      color: #c7c9d3;
+      margin-right: 15px;
+      font-weight: 700;
+     
+    }
+
     z-index: 0;
     a:first-child {
       @extend %buttonGrey;

+ 1 - 2
src/components/ImageUploader/ImageUploader.jsx

@@ -1,5 +1,4 @@
 import style from './ImageUploader.module.scss';
-import initialUserImage from '../../assets/img/initialUserImage.jpeg';
 import { usePopup } from '../../hooks/usePopup';
 import { DragAndDropPopup } from '../DragAndDropPopup/DragAndDropPopup';
 import { useSelector } from 'react-redux';
@@ -11,7 +10,7 @@ export const ImageUploader = () => {
   return (
     <>
       <div className={style.imageUploader}>
-        <img src={avatar ? avatar : initialUserImage} alt="userImage" />
+        <img src={avatar} alt="userImage" />
         <div className={style.info}>
           <span>Upload a New Profile Image</span>
           <button onClick={dndPopup.open}>Choose File</button>

+ 44 - 0
src/components/UpdatePassword/UpdatePassword.jsx

@@ -0,0 +1,44 @@
+import { Input } from '../Input/Input';
+import style from './UpdatePassword.module.scss';
+import { useInput } from '../../hooks/useInput';
+import { useDispatch, useSelector } from 'react-redux';
+import { changePassword } from '../../store/user/actions/changePassword';
+
+export const UpdatePassword = () => {
+  const dispatch = useDispatch();
+  const currentPassword = useInput('');
+  const newPassword = useInput('');
+  const { login } = useSelector((state) => state.user);
+
+  const handleSubmit = (e) => {
+    e.preventDefault();
+
+    const newPasswordData = {
+      login,
+      password: currentPassword.value,
+      newPassword: newPassword.value,
+    };
+
+    dispatch(changePassword(newPasswordData));
+    currentPassword.setValue('');
+    newPassword.setValue('');
+  };
+
+  return (
+    <form className={style.updatePassword} onSubmit={handleSubmit}>
+      <Input
+        value={currentPassword.value}
+        onChange={currentPassword.onChange}
+        title="Current Password"
+        type="password"
+      />
+      <Input
+        value={newPassword.value}
+        onChange={newPassword.onChange}
+        title="New Password"
+        type="password"
+      />
+      <button type="submit">Update</button>
+    </form>
+  );
+};

+ 33 - 0
src/components/UpdatePassword/UpdatePassword.module.scss

@@ -0,0 +1,33 @@
+@import '../../scss/index.scss';
+
+.updatePassword {
+  @include container();
+  color: #fdfdfd;
+  width: 65%;
+  height: auto;
+  border-radius: 6px;
+  background-color: #1e1f26;
+  padding: 30px;
+  display: flex;
+  flex-direction: column;
+
+  label {
+    font-weight: 700;
+    font-size: 18px;
+    
+    input {
+      width: 100%;
+      margin-top: 8px;
+    }
+  }
+
+  button {
+    @extend %buttonGrey;
+    font-size: 16px;
+    font-weight: 600;
+    width: 160px;
+   
+  }
+}
+
+

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

@@ -9,6 +9,7 @@ export const UserPopup = ({ isOpen, popupRef }) => {
 
   const handleLogout = () => {
     localStorage.removeItem('authToken');
+
     navigate('/');
     dispatch(logout());
   };
@@ -17,7 +18,6 @@ export const UserPopup = ({ isOpen, popupRef }) => {
   return (
     <ul ref={popupRef} className={style.userPopup}>
       <li onClick={() => navigate('/your-works')}>Your Works</li>
-      <li>New Pen</li>
       <hr />
       <li onClick={() => navigate('/settings')}>Settings</li>
       <li onClick={handleLogout}>Log Out</li>

+ 3 - 37
src/hooks/useConsole.js

@@ -1,40 +1,5 @@
 import { useEffect, useState } from 'react';
 
-// export const useConsole = () => {
-//   const [history, setHistory] = useState([]);
-
-//   function addResult(inputAsString, output) {
-//     const outputAsString = Array.isArray(output)
-//       ? `[${output.join(', ')}]`
-//       : output.toString();
-//     setHistory((prevHistory) => [
-//       ...prevHistory,
-//       { input: inputAsString, output: outputAsString },
-//     ]);
-//   }
-
-//   const readCode = (code) => {
-//     if (code.length === 0) return;
-
-//     const strings = code.split('\n').filter((item) => item.length);
-
-//     strings.forEach((string) => {
-//       try {
-//         addResult(string, eval(string));
-//       } catch (err) {
-//         addResult(string, err);
-//       }
-//     });
-//   };
-
-//   return {
-//     history,
-//     readCode,
-//     addResult,
-//     handleInputKeyUp,
-//   };
-// };
-
 export const useConsole = () => {
   const [output, setOutput] = useState([]);
   const [command, setCommand] = useState('');
@@ -58,8 +23,9 @@ export const useConsole = () => {
     strings.forEach((string) => {
       try {
         const result = eval(string);
-
-        setOutput((output) => [...output, result]);
+        if (result) {
+          setOutput((output) => [...output, result]);
+        }
       } catch (e) {
         setOutput((output) => [...output, e.toString()]);
       }

+ 16 - 0
src/layouts/PrivateRoute.jsx

@@ -0,0 +1,16 @@
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+
+export const PrivateRoute = ({ children }) => {
+  const { isAuth } = useSelector((state) => state.auth);
+  const navigate = useNavigate();
+
+  useEffect(() => {
+    if (!isAuth) {
+      return navigate('/login');
+    }
+  }, []);
+
+  return children;
+};

+ 1 - 1
src/pages/HomePage/HomePage.jsx

@@ -31,7 +31,7 @@ export const HomePage = () => {
           </p>
 
           <Link className={style.link} to={isAuth ? '/your-works' : '/pen'}>
-            Start Codding
+            {isAuth ? 'Your works' : 'Start Codding'}
           </Link>
         </div>
         <div>

+ 6 - 2
src/pages/SettingsPage/SettingsPage.jsx

@@ -2,6 +2,7 @@ import React from 'react';
 import { Header } from '../../components/Header/Header';
 import style from './SettingsPage.module.scss';
 import { ImageUploader } from '../../components/ImageUploader/ImageUploader';
+import { UpdatePassword } from '../../components/UpdatePassword/UpdatePassword';
 
 export const SettingsPage = () => {
   return (
@@ -14,8 +15,11 @@ export const SettingsPage = () => {
         <h2>Profile Image</h2>
         <ImageUploader />
 
-        <h2>About You</h2>
-        <div className={style.imageSection}></div>
+        <div className={style.updatePassword}>
+          <h2>Update Password</h2>
+
+          <UpdatePassword />
+        </div>
       </div>
     </div>
   );

+ 25 - 25
src/pages/SettingsPage/SettingsPage.module.scss

@@ -1,33 +1,33 @@
 @import '../../scss/index.scss';
 
 .settings {
-    height: 100vh;
-    background-color: #131417;
+  height: 100vh;
+  background-color: #131417;
 
-    .container {
-        @include container();
-        margin-top: 20px;
-        color: #FDFDFD;
-       
+  .container {
+    @include container();
+    margin-top: 20px;
+    color: #fdfdfd;
 
-        h1 {
-            padding-bottom: 13px;
-            border-bottom: 2px solid $greenMain;
-            margin-bottom: 35px;
-        }
-
-        h2:first-of-type {
-            display: inline-block;
-            margin-bottom: 260px;
-            width: 35%;   
-        }
-
-        h2:nth-of-type(2) {
-            display: inline-block;
-            width: 35%;   
-        }
-      
+    h1 {
+      padding-bottom: 13px;
+      border-bottom: 2px solid $greenMain;
+      margin-bottom: 35px;
+    }
 
+    h2:first-of-type {
+      display: inline-block;
+      margin-bottom: 260px;
+      width: 35%;
+    }
 
+    h2:nth-of-type(2) {
+      display: inline-block;
+      width: 35%;
     }
-}
+  }
+
+  .updatePassword {
+    display: flex;
+  }
+}

+ 24 - 15
src/store/user/actions/changeAvatar.js

@@ -1,6 +1,7 @@
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { getGql } from '../../../services/api';
 import { uploadImage } from './uploadImage';
+import initialUserAvatar from '../../../assets/img/initialUserImage.jpeg';
 
 export const changeAvatar = createAsyncThunk(
   'user/changeAvatar',
@@ -8,8 +9,6 @@ export const changeAvatar = createAsyncThunk(
   async ({ user, file }, { rejectWithValue, dispatch }) => {
     const gql = getGql();
 
-    console.log({ user, file });
-
     try {
       const imageAction = await dispatch(uploadImage(file));
 
@@ -19,22 +18,32 @@ export const changeAvatar = createAsyncThunk(
 
       const response = await gql.request(
         `
-            mutation setAvatar{
-              UserUpsert(user:{_id: "${user.id}", avatar: {_id: "${imageAction.payload._id}"}}){
-                  _id,
-                  avatar{
-                      _id
-                      url
-                      text
-                      userAvatar {
-                        login
-                      }
-                  }
+        mutation setAvatar($userId: String!, $imageId: ID!) {
+          UserUpsert(
+            user: {
+              _id: $userId,
+              avatar: {
+                _id: $imageId
               }
+            }
+          ) {
+            _id
+            avatar {
+              url
+            }
           }
-          `
+        }
+        `,
+        {
+          userId: user.id,
+          imageId: imageAction.payload._id,
+        }
       );
-      return response.UserUpsert.avatar.url;
+      if (response.UserUpsert.avatar.url) {
+        return response.UserUpsert.avatar.url;
+      } else {
+        return initialUserAvatar;
+      }
     } catch (error) {
       console.log(error);
       return rejectWithValue('Something went wrong please try again.');

+ 37 - 0
src/store/user/actions/changePassword.js

@@ -0,0 +1,37 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { GraphQLClient } from 'graphql-request';
+
+export const changePassword = createAsyncThunk(
+  'user/changePassword',
+
+  async ({ login, password, newPassword }, { rejectWithValue }) => {
+    const gql = new GraphQLClient('/graphql', {
+      headers: {
+        'Content-Type': 'application/json;charset=utf-8',
+        Accept: 'application/json',
+      },
+    });
+
+    try {
+      const response = await gql.request(
+        ` 
+        mutation changePassword($login: String!, $password: String!, $newPassword: String!) {
+            changePassword(login: $login, password: $password, newPassword: $newPassword) {
+              _id
+              login
+            }
+          }
+          `,
+        {
+          login,
+          password,
+          newPassword,
+        }
+      );
+      return response.changePassword;
+    } catch (error) {
+      console.log(error);
+      return rejectWithValue('Something went wrong please try again.');
+    }
+  }
+);

+ 19 - 2
src/store/user/userSlice.js

@@ -2,12 +2,14 @@ import { createSlice } from '@reduxjs/toolkit';
 import { changeAvatar } from './actions/changeAvatar';
 import { fetchUser } from './actions/fetchUser';
 import { uploadImage } from './actions/uploadImage';
+import { changePassword } from './actions/changePassword';
+import initialUserAvatar from '../../assets/img/initialUserImage.jpeg';
 
 const initialState = {
   id: '',
   login: '',
   nick: '',
-  avatar: '',
+  avatar: initialUserAvatar,
   isLoading: false,
 };
 
@@ -26,7 +28,11 @@ export const userSlice = createSlice({
       state.isLoading = false;
       state.id = action.payload._id;
       state.login = action.payload.login;
-      state.avatar = `${proxy}${action.payload.avatar.url}`;
+      if (action.payload?.avatar?.url) {
+        state.avatar = `${proxy}${action.payload.avatar.url}`;
+      } else {
+        state.avatar = initialUserAvatar;
+      }
     });
 
     builder.addCase(fetchUser.rejected, (state, action) => {
@@ -58,5 +64,16 @@ export const userSlice = createSlice({
     builder.addCase(changeAvatar.fulfilled, (state, action) => {
       state.avatar = `${proxy}${action.payload}`;
     });
+
+    // changePassword
+    builder.addCase(changePassword.pending, (state) => {
+      state.isLoading = true;
+    });
+    builder.addCase(changePassword.rejected, (state) => {
+      state.isLoading = false;
+    });
+    builder.addCase(changePassword.fulfilled, (state, action) => {
+      state.isLoading = false;
+    });
   },
 });