@@ -1,3 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
default.json
|
default.json
|
||||||
.vscode
|
.vscode
|
||||||
|
TODO.txt
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
Generated
+6740
-5509
File diff suppressed because it is too large
Load Diff
+6
-10
@@ -1,26 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "my-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.4.0",
|
||||||
"moment": "^2.29.4",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-moment": "^1.1.2",
|
"react-redux": "^8.0.5",
|
||||||
"react-redux": "^8.0.4",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-router-dom": "^6.4.2",
|
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"redux": "^4.2.0",
|
|
||||||
"redux-devtools-extension": "^2.13.9",
|
|
||||||
"redux-thunk": "^2.4.1",
|
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start --openssl-legacy-provider start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link
|
<link
|
||||||
@@ -11,11 +11,39 @@
|
|||||||
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
|
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<title>Welcome To DevConnector</title>
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -6,6 +6,16 @@
|
|||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/* Global Styles */
|
/* Global Styles */
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #17a2b8;
|
--primary-color: #17a2b8;
|
||||||
|
|||||||
+16
-2
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import Navbar from './components/layout/Navbar';
|
import Navbar from './components/layout/Navbar';
|
||||||
import Landing from './components/layout/Landing';
|
import Landing from './components/layout/Landing';
|
||||||
import Register from './components/auth/Register';
|
import Register from './components/auth/Register';
|
||||||
@@ -15,7 +16,6 @@ import Posts from './components/posts/Posts';
|
|||||||
import Post from './components/post/Post';
|
import Post from './components/post/Post';
|
||||||
import NotFound from './components/layout/NotFound';
|
import NotFound from './components/layout/NotFound';
|
||||||
import PrivateRoute from './components/routing/PrivateRoute';
|
import PrivateRoute from './components/routing/PrivateRoute';
|
||||||
import { LOGOUT } from './actions/types';
|
|
||||||
|
|
||||||
// Redux
|
// Redux
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
@@ -24,6 +24,10 @@ import { loadUser } from './actions/auth';
|
|||||||
import setAuthToken from './utils/setAuthToken';
|
import setAuthToken from './utils/setAuthToken';
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { logOut } from './reducers/auth';
|
||||||
|
|
||||||
|
// Level - 1
|
||||||
|
//
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,7 +42,7 @@ const App = () => {
|
|||||||
|
|
||||||
// log user out from all tabs if they log out in one tab
|
// log user out from all tabs if they log out in one tab
|
||||||
window.addEventListener("storage", () => {
|
window.addEventListener("storage", () => {
|
||||||
if (!localStorage.token) store.dispatch({ type: LOGOUT });
|
if (!localStorage.token) store.dispatch(logOut);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -80,6 +84,16 @@ const App = () => {
|
|||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
/*
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router>
|
||||||
|
<Routes>
|
||||||
|
<Route path="login" element={<Login />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
);*/
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { SET_ALERT, REMOVE_ALERT } from './types';
|
import {removeAlert, setAlert } from '../reducers/alert';
|
||||||
|
|
||||||
export const setAlert = (msg, alertType, timeout = 5000) => dispatch => {
|
export const createAlert = (msg, alertType, timeout = 5000) => dispatch => {
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
dispatch({
|
dispatch(setAlert({ msg, alertType, id }));
|
||||||
type: SET_ALERT,
|
|
||||||
payload: { msg, alertType, id }
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
|
setTimeout(() => dispatch(removeAlert(id)), timeout);
|
||||||
};
|
};
|
||||||
|
|||||||
+33
-51
@@ -1,15 +1,12 @@
|
|||||||
import api from "../utils/api";
|
import api from "../utils/api";
|
||||||
import { setAlert } from "./alert";
|
import { createAlert } from "./alert";
|
||||||
import {
|
import {
|
||||||
REGISTER_SUCCESS,
|
loginSucces,
|
||||||
REGISTER_FAIL,
|
authError,
|
||||||
USER_LOADED,
|
registerSuccess,
|
||||||
AUTH_ERROR,
|
logOut,
|
||||||
LOGIN_SUCCESS,
|
userLoaded,
|
||||||
LOGIN_FAIL,
|
} from "../reducers/auth";
|
||||||
LOGOUT,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
NOTE: we don't need a config object for axios as the
|
NOTE: we don't need a config object for axios as the
|
||||||
default headers in axios are already Content-Type: application/json
|
default headers in axios are already Content-Type: application/json
|
||||||
@@ -17,19 +14,32 @@ import {
|
|||||||
JSON.stringify or JSON.parse
|
JSON.stringify or JSON.parse
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Load User
|
export const login = (email, password) => async (dispatch) => {
|
||||||
|
const body = { email, password };
|
||||||
|
try {
|
||||||
|
const res = await api.post("/auth", body);
|
||||||
|
|
||||||
|
dispatch(loginSucces(res.data));
|
||||||
|
|
||||||
|
dispatch(loadUser());
|
||||||
|
} catch (err) {
|
||||||
|
const errors = err.response.data.errors;
|
||||||
|
if (errors) {
|
||||||
|
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: "auth/loginFail",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const loadUser = () => async (dispatch) => {
|
export const loadUser = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/auth");
|
const res = await api.get("/auth");
|
||||||
dispatch({
|
|
||||||
type: USER_LOADED,
|
dispatch(userLoaded(res.data));
|
||||||
payload: res.data,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(authError());
|
||||||
type: AUTH_ERROR,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,49 +48,21 @@ export const register = (formData) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.post("/users", formData);
|
const res = await api.post("/users", formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(registerSuccess(res.data));
|
||||||
type: REGISTER_SUCCESS,
|
|
||||||
payload: res.data,
|
|
||||||
});
|
|
||||||
dispatch(loadUser());
|
dispatch(loadUser());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errors = err.response.data.errors;
|
const errors = err.response.data.errors;
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
|
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: REGISTER_FAIL,
|
type: "auth/registerFail",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Login User
|
export const logout = () => async (dispatch) => {
|
||||||
export const login = (email, password) => async (dispatch) => {
|
dispatch(logOut);
|
||||||
const body = { email, password };
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await api.post("/auth", body);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: LOGIN_SUCCESS,
|
|
||||||
payload: res.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(loadUser());
|
|
||||||
} catch (err) {
|
|
||||||
const errors = err.response.data.errors;
|
|
||||||
|
|
||||||
if (errors) {
|
|
||||||
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: LOGIN_FAIL,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Logout
|
|
||||||
export const logout = () => ({ type: LOGOUT });
|
|
||||||
|
|||||||
+49
-88
@@ -1,37 +1,26 @@
|
|||||||
import api from '../utils/api';
|
|
||||||
import { setAlert } from './alert';
|
|
||||||
import {
|
import {
|
||||||
GET_POSTS,
|
removeComment,
|
||||||
POST_ERROR,
|
addCommentAction,
|
||||||
UPDATE_LIKES,
|
updateLikes,
|
||||||
DELETE_POST,
|
postError,
|
||||||
ADD_POST,
|
deletePostAction,
|
||||||
GET_POST,
|
getPostAction,
|
||||||
ADD_COMMENT,
|
getPostsAction,
|
||||||
REMOVE_COMMENT
|
addPostAction,
|
||||||
} from './types';
|
} from "../reducers/post";
|
||||||
|
import api from "../utils/api";
|
||||||
/*
|
import { createAlert } from "./alert";
|
||||||
NOTE: we don't need a config object for axios as the
|
|
||||||
default headers in axios are already Content-Type: application/json
|
|
||||||
also axios stringifies and parses JSON for you, so no need for
|
|
||||||
JSON.stringify or JSON.parse
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get posts
|
// Get posts
|
||||||
export const getPosts = () => async (dispatch) => {
|
export const getPosts = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/posts');
|
const res = await api.get("/posts");
|
||||||
|
|
||||||
dispatch({
|
dispatch(getPostsAction(res.data));
|
||||||
type: GET_POSTS,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,15 +29,11 @@ export const addLike = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.put(`/posts/like/${id}`);
|
const res = await api.put(`/posts/like/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateLikes({ id, likes: res.data }));
|
||||||
type: UPDATE_LIKES,
|
|
||||||
payload: { id, likes: res.data }
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,15 +42,11 @@ export const removeLike = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.put(`/posts/unlike/${id}`);
|
const res = await api.put(`/posts/unlike/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateLikes({ id, likes: res.data }));
|
||||||
type: UPDATE_LIKES,
|
|
||||||
payload: { id, likes: res.data }
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,36 +55,28 @@ export const deletePost = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
await api.delete(`/posts/${id}`);
|
await api.delete(`/posts/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(deletePostAction(id));
|
||||||
type: DELETE_POST,
|
|
||||||
payload: id
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Post Removed', 'success'));
|
dispatch(createAlert("Post Removed", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add post
|
// Add post
|
||||||
export const addPost = (formData) => async (dispatch) => {
|
export const addPost = (formData) => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.post('/posts', formData);
|
const res = await api.post("/posts", formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(addPostAction(res.data));
|
||||||
type: ADD_POST,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Post Created', 'success'));
|
dispatch(createAlert("Post Created", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,15 +85,11 @@ export const getPost = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.get(`/posts/${id}`);
|
const res = await api.get(`/posts/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(getPostAction(res.data));
|
||||||
type: GET_POST,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,17 +98,13 @@ export const addComment = (postId, formData) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.post(`/posts/comment/${postId}`, formData);
|
const res = await api.post(`/posts/comment/${postId}`, formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(addCommentAction(res.data));
|
||||||
type: ADD_COMMENT,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Comment Added', 'success'));
|
dispatch(createAlert("Comment Added", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,16 +113,12 @@ export const deleteComment = (postId, commentId) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
await api.delete(`/posts/comment/${postId}/${commentId}`);
|
await api.delete(`/posts/comment/${postId}/${commentId}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(removeComment(commentId));
|
||||||
type: REMOVE_COMMENT,
|
|
||||||
payload: commentId
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Comment Removed', 'success'));
|
dispatch(createAlert("Comment Removed", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: POST_ERROR,
|
postError({ msg: err.response.statusText, status: err.response.status })
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+97
-120
@@ -1,57 +1,48 @@
|
|||||||
import api from '../utils/api';
|
import api from "../utils/api";
|
||||||
import { setAlert } from './alert';
|
import { createAlert } from "./alert";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GET_PROFILE,
|
noRepos,
|
||||||
GET_PROFILES,
|
getRepos,
|
||||||
PROFILE_ERROR,
|
clearProfile,
|
||||||
UPDATE_PROFILE,
|
profileError,
|
||||||
CLEAR_PROFILE,
|
getProfilesType,
|
||||||
ACCOUNT_DELETED,
|
updateProfile,
|
||||||
GET_REPOS,
|
getProfile,
|
||||||
NO_REPOS
|
} from "../reducers/profile";
|
||||||
} from './types';
|
import { accountDeleted } from "../reducers/auth";
|
||||||
|
|
||||||
/*
|
|
||||||
NOTE: we don't need a config object for axios as the
|
|
||||||
default headers in axios are already Content-Type: application/json
|
|
||||||
also axios stringifies and parses JSON for you, so no need for
|
|
||||||
JSON.stringify or JSON.parse
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get current users profile
|
// Get current users profile
|
||||||
export const getCurrentProfile = () => async (dispatch) => {
|
export const getCurrentProfile = () => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/profile/me');
|
const res = await api.get("/profile/me");
|
||||||
|
|
||||||
dispatch({
|
dispatch(getProfile(res.data));
|
||||||
type: GET_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all profiles
|
// Get all profiles
|
||||||
export const getProfiles = () => async (dispatch) => {
|
export const getProfiles = () => async (dispatch) => {
|
||||||
dispatch({ type: CLEAR_PROFILE });
|
dispatch(clearProfile());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/profile');
|
const res = await api.get("/profile");
|
||||||
|
|
||||||
dispatch({
|
dispatch(getProfilesType(res.data));
|
||||||
type: GET_PROFILES,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,15 +51,14 @@ export const getProfileById = (userId) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.get(`/profile/user/${userId}`);
|
const res = await api.get(`/profile/user/${userId}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(getProfile(res.data));
|
||||||
type: GET_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,101 +67,88 @@ export const getGithubRepos = (username) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.get(`/profile/github/${username}`);
|
const res = await api.get(`/profile/github/${username}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(getRepos(res.data));
|
||||||
type: GET_REPOS,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(noRepos());
|
||||||
type: NO_REPOS
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create or update profile
|
// Create or update profile
|
||||||
export const createProfile =
|
export const createProfile =
|
||||||
(formData, navigate, edit = false) =>
|
(formData, edit = false) =>
|
||||||
async (dispatch) => {
|
async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.post('/profile', formData);
|
const res = await api.post("/profile", formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(getProfile(res.data));
|
||||||
type: GET_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setAlert(edit ? 'Profile Updated' : 'Profile Created', 'success')
|
createAlert(edit ? "Profile Updated" : "Profile Created", "success")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!edit) {
|
|
||||||
navigate('/dashboard');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errors = err.response.data.errors;
|
const errors = err.response.data.errors;
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
|
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add Experience
|
// Add Experience
|
||||||
export const addExperience = (formData, navigate) => async (dispatch) => {
|
export const addExperience = (formData) => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.put('/profile/experience', formData);
|
const res = await api.put("/profile/experience", formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateProfile(res.data));
|
||||||
type: UPDATE_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Experience Added', 'success'));
|
dispatch(createAlert("Experience Added", "success"));
|
||||||
|
|
||||||
navigate('/dashboard');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errors = err.response.data.errors;
|
const errors = err.response.data.errors;
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
|
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add Education
|
// Add Education
|
||||||
export const addEducation = (formData, navigate) => async (dispatch) => {
|
export const addEducation = (formData) => async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.put('/profile/education', formData);
|
const res = await api.put("/profile/education", formData);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateProfile(res.data));
|
||||||
type: UPDATE_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Education Added', 'success'));
|
dispatch(createAlert("Education Added", "success"));
|
||||||
|
|
||||||
navigate('/dashboard');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errors = err.response.data.errors;
|
const errors = err.response.data.errors;
|
||||||
|
|
||||||
if (errors) {
|
if (errors) {
|
||||||
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
|
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,17 +157,16 @@ export const deleteExperience = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.delete(`/profile/experience/${id}`);
|
const res = await api.delete(`/profile/experience/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateProfile(res.data));
|
||||||
type: UPDATE_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Experience Removed', 'success'));
|
dispatch(createAlert("Experience Removed", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,35 +175,36 @@ export const deleteEducation = (id) => async (dispatch) => {
|
|||||||
try {
|
try {
|
||||||
const res = await api.delete(`/profile/education/${id}`);
|
const res = await api.delete(`/profile/education/${id}`);
|
||||||
|
|
||||||
dispatch({
|
dispatch(updateProfile(res.data));
|
||||||
type: UPDATE_PROFILE,
|
|
||||||
payload: res.data
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setAlert('Education Removed', 'success'));
|
dispatch(createAlert("Education Removed", "success"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete account & profile
|
// Delete account & profile
|
||||||
export const deleteAccount = () => async (dispatch) => {
|
export const deleteAccount = () => async (dispatch) => {
|
||||||
if (window.confirm('Are you sure? This can NOT be undone!')) {
|
if (window.confirm("Are you sure? This can NOT be undone!")) {
|
||||||
try {
|
try {
|
||||||
await api.delete('/profile');
|
await api.delete("/profile");
|
||||||
|
|
||||||
dispatch({ type: CLEAR_PROFILE });
|
dispatch(clearProfile());
|
||||||
dispatch({ type: ACCOUNT_DELETED });
|
dispatch(accountDeleted());
|
||||||
|
|
||||||
dispatch(setAlert('Your account has been permanently deleted'));
|
dispatch(createAlert("Your account has been permanently deleted"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({
|
dispatch(
|
||||||
type: PROFILE_ERROR,
|
profileError({
|
||||||
payload: { msg: err.response.statusText, status: err.response.status }
|
msg: err.response.statusText,
|
||||||
});
|
status: err.response.status,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
export const SET_ALERT = 'SET_ALERT';
|
|
||||||
export const REMOVE_ALERT = 'REMOVE_ALERT';
|
|
||||||
export const REGISTER_SUCCESS = 'REGISTER_SUCCESS';
|
|
||||||
export const REGISTER_FAIL = 'REGISTER_FAIL';
|
|
||||||
export const USER_LOADED = 'USER_LOADED';
|
|
||||||
export const AUTH_ERROR = 'AUTH_ERROR';
|
|
||||||
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
|
|
||||||
export const LOGIN_FAIL = 'LOGIN_FAIL';
|
|
||||||
export const LOGOUT = 'LOGOUT';
|
|
||||||
export const GET_PROFILE = 'GET_PROFILE';
|
|
||||||
export const GET_PROFILES = 'GET_PROFILES';
|
|
||||||
export const GET_REPOS = 'GET_REPOS';
|
|
||||||
export const NO_REPOS = 'NO_REPOS';
|
|
||||||
export const UPDATE_PROFILE = 'UPDATE_PROFILE';
|
|
||||||
export const CLEAR_PROFILE = 'CLEAR_PROFILE';
|
|
||||||
export const PROFILE_ERROR = 'PROFILE_ERROR';
|
|
||||||
export const ACCOUNT_DELETED = 'ACCOUNT_DELETED';
|
|
||||||
export const GET_POSTS = 'GET_POSTS';
|
|
||||||
export const GET_POST = 'GET_POST';
|
|
||||||
export const POST_ERROR = 'POST_ERROR';
|
|
||||||
export const UPDATE_LIKES = 'UPDATE_LIKES';
|
|
||||||
export const DELETE_POST = 'DELETE_POST';
|
|
||||||
export const ADD_POST = 'ADD_POST';
|
|
||||||
export const ADD_COMMENT = 'ADD_COMMENT';
|
|
||||||
export const REMOVE_COMMENT = 'REMOVE_COMMENT';
|
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { Link, Navigate } from 'react-router-dom';
|
import { Link, Navigate } from "react-router-dom";
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import PropTypes from 'prop-types';
|
import { login } from "../../actions/auth";
|
||||||
import { login } from '../../actions/auth';
|
|
||||||
|
|
||||||
const Login = ({ login, isAuthenticated }) => {
|
const Login = () => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: '',
|
email: "",
|
||||||
password: ''
|
password: "",
|
||||||
});
|
});
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { email, password } = formData;
|
const { email, password } = formData;
|
||||||
|
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
|
||||||
const onChange = (e) =>
|
const onChange = (e) =>
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
login(email, password);
|
await dispatch(login(email, password));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
@@ -59,13 +58,4 @@ const Login = ({ login, isAuthenticated }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Login.propTypes = {
|
export default Login;
|
||||||
login: PropTypes.func.isRequired,
|
|
||||||
isAuthenticated: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
isAuthenticated: state.auth.isAuthenticated
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { login })(Login);
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch,useSelector } from 'react-redux';
|
||||||
import { Link, Navigate } from 'react-router-dom';
|
import { Link, Navigate } from 'react-router-dom';
|
||||||
import { setAlert } from '../../actions/alert';
|
import { createAlert } from '../../actions/alert';
|
||||||
import { register } from '../../actions/auth';
|
import { register } from '../../actions/auth';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const Register = ({ setAlert, register, isAuthenticated }) => {
|
|
||||||
|
const Register = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
password2: ''
|
password2: ''
|
||||||
});
|
});
|
||||||
|
const isAuthenticated = useSelector(state=>state.auth.isAuthenticated)
|
||||||
const { name, email, password, password2 } = formData;
|
const { name, email, password, password2 } = formData;
|
||||||
|
|
||||||
const onChange = (e) =>
|
const onChange = (e) =>
|
||||||
@@ -21,9 +22,9 @@ const Register = ({ setAlert, register, isAuthenticated }) => {
|
|||||||
const onSubmit = async (e) => {
|
const onSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (password !== password2) {
|
if (password !== password2) {
|
||||||
setAlert('Passwords do not match', 'danger');
|
await dispatch(createAlert('Passwords do not match', 'danger'));
|
||||||
} else {
|
} else {
|
||||||
register({ name, email, password });
|
await dispatch(register({ name, email, password }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,14 +88,4 @@ const Register = ({ setAlert, register, isAuthenticated }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Register.propTypes = {
|
export default Register;
|
||||||
setAlert: PropTypes.func.isRequired,
|
|
||||||
register: PropTypes.func.isRequired,
|
|
||||||
isAuthenticated: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
isAuthenticated: state.auth.isAuthenticated
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { setAlert, register })(Register);
|
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import DashboardActions from "./DashboardActions";
|
||||||
|
import Experience from "./Experience";
|
||||||
|
import Education from "./Education";
|
||||||
|
import { getCurrentProfile, deleteAccount } from "../../actions/profile";
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
const Dashboard = () => {
|
||||||
import { Link } from 'react-router-dom';
|
const dispatch = useDispatch();
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import DashboardActions from './DashboardActions';
|
|
||||||
import Experience from './Experience';
|
|
||||||
import Education from './Education';
|
|
||||||
import { getCurrentProfile, deleteAccount } from '../../actions/profile';
|
|
||||||
|
|
||||||
|
|
||||||
const Dashboard = ({
|
|
||||||
getCurrentProfile,
|
|
||||||
deleteAccount,
|
|
||||||
auth: { user },
|
|
||||||
profile: { profile }
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCurrentProfile();
|
function fetchData() {
|
||||||
}, [getCurrentProfile]);
|
dispatch(getCurrentProfile());
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch]);
|
||||||
|
const user = useSelector((state) => state.auth.user);
|
||||||
|
const profile = useSelector((state) => state.profile.profile);
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<section className="container">
|
||||||
<h1 className="large text-primary">Dashboard</h1>
|
<h1 className="large text-primary">Dashboard</h1>
|
||||||
@@ -32,8 +29,11 @@ const Dashboard = ({
|
|||||||
<Education education={profile.education} />
|
<Education education={profile.education} />
|
||||||
|
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<button className="btn btn-danger" onClick={() => deleteAccount()}>
|
<button
|
||||||
<i className="fas fa-user-minus" /> Delete My Account
|
className="btn btn-danger"
|
||||||
|
onClick={async () => await dispatch(deleteAccount())}
|
||||||
|
>
|
||||||
|
<i className="fas fa-user" /> Delete My Account
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -49,18 +49,4 @@ const Dashboard = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Dashboard.propTypes = {
|
export default Dashboard;
|
||||||
getCurrentProfile: PropTypes.func.isRequired,
|
|
||||||
deleteAccount: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.object.isRequired,
|
|
||||||
profile: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
auth: state.auth,
|
|
||||||
profile: state.profile,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getCurrentProfile, deleteAccount })(
|
|
||||||
Dashboard
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import {useDispatch } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import { deleteEducation } from "../../actions/profile";
|
||||||
import { deleteEducation } from '../../actions/profile';
|
import formatDate from "../../utils/formatDate";
|
||||||
import formatDate from '../../utils/formatDate';
|
|
||||||
|
|
||||||
const Education = ({ education, deleteEducation }) => {
|
const Education = ({education}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const educations = education.map((edu) => (
|
const educations = education.map((edu) => (
|
||||||
<tr key={edu._id}>
|
<tr key={edu._id}>
|
||||||
<td>{edu.school}</td>
|
<td>{edu.school}</td>
|
||||||
<td className="hide-sm">{edu.degree}</td>
|
<td className="hide-sm">{edu.degree}</td>
|
||||||
<td>
|
<td>
|
||||||
{formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : 'Now'}
|
{formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : "Now"}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteEducation(edu._id)}
|
onClick={async () => await dispatch(deleteEducation(edu._id))}
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -41,9 +41,4 @@ const Education = ({ education, deleteEducation }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Education.propTypes = {
|
export default Education;
|
||||||
education: PropTypes.array.isRequired,
|
|
||||||
deleteEducation: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(null, { deleteEducation })(Education);
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import {useDispatch } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { deleteExperience } from '../../actions/profile';
|
import { deleteExperience } from '../../actions/profile';
|
||||||
import formatDate from '../../utils/formatDate';
|
import formatDate from '../../utils/formatDate';
|
||||||
|
|
||||||
const Experience = ({ experience, deleteExperience }) => {
|
const Experience = ({experience}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const experiences = experience.map((exp) => (
|
const experiences = experience.map((exp) => (
|
||||||
<tr key={exp._id}>
|
<tr key={exp._id}>
|
||||||
<td>{exp.company}</td>
|
<td>{exp.company}</td>
|
||||||
@@ -14,7 +14,7 @@ const Experience = ({ experience, deleteExperience }) => {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteExperience(exp._id)}
|
onClick={async () => dispatch(deleteExperience(exp._id))}
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -41,9 +41,6 @@ const Experience = ({ experience, deleteExperience }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Experience.propTypes = {
|
|
||||||
experience: PropTypes.array.isRequired,
|
|
||||||
deleteExperience: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(null, { deleteExperience })(Experience);
|
|
||||||
|
export default Experience;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
const Alert = () => {
|
||||||
|
const alerts = useSelector((state) => state.alert);
|
||||||
const Alert = ({ alerts }) => (
|
return (
|
||||||
<div className="alert-wrapper">
|
<div className="alert-wrapper">
|
||||||
{alerts.map((alert) => (
|
{alerts.map((alert) => (
|
||||||
<div key={alert.id} className={`alert alert-${alert.alertType}`}>
|
<div key={alert.id} className={`alert alert-${alert.alertType}`}>
|
||||||
@@ -11,13 +11,6 @@ const Alert = ({ alerts }) => (
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
Alert.propTypes = {
|
|
||||||
alerts: PropTypes.array.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
export default Alert;
|
||||||
alerts: state.alert
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(Alert);
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Link, Navigate } from 'react-router-dom';
|
import { Link, Navigate } from "react-router-dom";
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from "react-redux";
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const Landing = ({ isAuthenticated }) => {
|
const Landing = () => {
|
||||||
|
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Navigate to="/dashboard" />;
|
return <Navigate to="/dashboard" />;
|
||||||
}
|
}
|
||||||
@@ -31,13 +31,4 @@ const Landing = ({ isAuthenticated }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Landing.propTypes = {
|
export default Landing;
|
||||||
isAuthenticated: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
isAuthenticated: state.auth.isAuthenticated
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(Landing);
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import PropTypes from 'prop-types';
|
import { logOut } from "../../reducers/auth";
|
||||||
import { logout } from '../../actions/auth';
|
|
||||||
|
|
||||||
const Navbar = ({ auth: { isAuthenticated }, logout }) => {
|
const Navbar = () => {
|
||||||
|
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const authLinks = (
|
const authLinks = (
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -15,12 +16,11 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to="/dashboard">
|
<Link to="/dashboard">
|
||||||
<i className="fas fa-user" />{' '}
|
<i className="fas fa-user" /> <span className="hide-sm">Profile</span>
|
||||||
<span className="hide-sm">Dashboard</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a onClick={logout} href="#!">
|
<a onClick={()=>dispatch(logOut())} href="#!">
|
||||||
<i className="fas fa-sign-out-alt" />{' '}
|
<i className="fas fa-sign-out-alt" />{' '}
|
||||||
<span className="hide-sm">Logout</span>
|
<span className="hide-sm">Logout</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -45,7 +45,7 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
|
|||||||
return (
|
return (
|
||||||
<nav className="navbar bg-dark">
|
<nav className="navbar bg-dark">
|
||||||
<h1>
|
<h1>
|
||||||
<Link to="/">
|
<Link to="/posts">
|
||||||
<i className="fas fa-code" /> DevConnector
|
<i className="fas fa-code" /> DevConnector
|
||||||
</Link>
|
</Link>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -54,13 +54,4 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Navbar.propTypes = {
|
export default Navbar;
|
||||||
logout: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
auth: state.auth
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { logout })(Navbar);
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { addComment } from '../../actions/post';
|
import { addComment } from '../../actions/post';
|
||||||
|
|
||||||
const CommentForm = ({ postId, addComment }) => {
|
const CommentForm = ({ postId }) => {
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='post-form'>
|
<div className='post-form'>
|
||||||
@@ -13,9 +13,9 @@ const CommentForm = ({ postId, addComment }) => {
|
|||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
className='form my-1'
|
className='form my-1'
|
||||||
onSubmit={e => {
|
onSubmit={async(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addComment(postId, { text });
|
await dispatch(addComment(postId, { text }));
|
||||||
setText('');
|
setText('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -34,12 +34,6 @@ const CommentForm = ({ postId, addComment }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CommentForm.propTypes = {
|
|
||||||
addComment: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
export default CommentForm
|
||||||
null,
|
|
||||||
{ addComment }
|
|
||||||
)(CommentForm);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import formatDate from "../../utils/formatDate";
|
||||||
import formatDate from '../../utils/formatDate';
|
import { deleteComment } from "../../actions/post";
|
||||||
import { deleteComment } from '../../actions/post';
|
|
||||||
|
|
||||||
const CommentItem = ({
|
const CommentItem = ({
|
||||||
postId,
|
postId,
|
||||||
comment: { _id, text, name, avatar, user, date },
|
comment: { _id, text, name, avatar, user, date },
|
||||||
auth,
|
}) => {
|
||||||
deleteComment
|
const dispatch = useDispatch();
|
||||||
}) => (
|
const auth = useSelector((state) => state.auth);
|
||||||
|
return (
|
||||||
<div className="post bg-white p-1 my-1">
|
<div className="post bg-white p-1 my-1">
|
||||||
<div>
|
<div>
|
||||||
<Link to={`/profile/${user}`}>
|
<Link to={`/profile/${user}`}>
|
||||||
@@ -23,7 +23,7 @@ const CommentItem = ({
|
|||||||
<p className="post-date">Posted on {formatDate(date)}</p>
|
<p className="post-date">Posted on {formatDate(date)}</p>
|
||||||
{!auth.loading && user === auth.user._id && (
|
{!auth.loading && user === auth.user._id && (
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteComment(postId, _id)}
|
onClick={async () => await dispatch(deleteComment(postId, _id))}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
>
|
>
|
||||||
@@ -33,17 +33,6 @@ const CommentItem = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
CommentItem.propTypes = {
|
|
||||||
postId: PropTypes.string.isRequired,
|
|
||||||
comment: PropTypes.object.isRequired,
|
|
||||||
auth: PropTypes.object.isRequired,
|
|
||||||
deleteComment: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
export default CommentItem;
|
||||||
auth: state.auth
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { deleteComment })(CommentItem);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import Spinner from "../layout/Spinner";
|
||||||
import Spinner from '../layout/Spinner';
|
import PostItem from "../posts/PostItem";
|
||||||
import PostItem from '../posts/PostItem';
|
import CommentForm from "../post/CommentForm";
|
||||||
import CommentForm from '../post/CommentForm';
|
import CommentItem from "../post/CommentItem";
|
||||||
import CommentItem from '../post/CommentItem';
|
import { getPost } from "../../actions/post";
|
||||||
import { getPost } from '../../actions/post';
|
|
||||||
|
|
||||||
const Post = ({ getPost, post: { post, loading } }) => {
|
const Post = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { post, loading } = useSelector((state) => state.post);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPost(id);
|
async function fetchData() {
|
||||||
}, [getPost, id]);
|
await dispatch(getPost(id));
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
return loading || post === null ? (
|
return loading || post === null ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
@@ -32,14 +36,4 @@ const Post = ({ getPost, post: { post, loading } }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Post.propTypes = {
|
export default Post;
|
||||||
getPost: PropTypes.func.isRequired,
|
|
||||||
post: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
post: state.post
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getPost })(Post);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,36 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import { addPost } from "../../actions/post";
|
||||||
import { addPost } from '../../actions/post';
|
|
||||||
|
|
||||||
const PostForm = ({ addPost }) => {
|
|
||||||
const [text, setText] = useState('');
|
|
||||||
|
|
||||||
|
const PostForm = () => {
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
const dispatch = useDispatch();
|
||||||
return (
|
return (
|
||||||
<div className='post-form'>
|
<div className="post-form">
|
||||||
<div className='bg-primary p'>
|
<div className="bg-primary p">
|
||||||
<h3>Say Something...</h3>
|
<h3>Say Something...</h3>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form
|
||||||
className='form my-1'
|
className="form my-1"
|
||||||
onSubmit={e => {
|
onSubmit={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addPost({ text });
|
await dispatch(addPost({ text }));
|
||||||
setText('');
|
setText("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
name='text'
|
name="text"
|
||||||
cols='30'
|
cols="30"
|
||||||
rows='5'
|
rows="5"
|
||||||
placeholder='Create a post'
|
placeholder="Create a post"
|
||||||
value={text}
|
value={text}
|
||||||
onChange={e => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<input type='submit' className='btn btn-dark my-1' value='Submit' />
|
<input type="submit" className="btn btn-dark my-1" value="Submit" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PostForm.propTypes = {
|
export default PostForm;
|
||||||
addPost: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
{ addPost }
|
|
||||||
)(PostForm);
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { Link } from "react-router-dom";
|
||||||
import { Link } from 'react-router-dom';
|
import formatDate from "../../utils/formatDate";
|
||||||
import formatDate from '../../utils/formatDate';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import { addLike, removeLike, deletePost } from "../../actions/post";
|
||||||
import { addLike, removeLike, deletePost } from '../../actions/post';
|
|
||||||
|
|
||||||
const PostItem = ({
|
const PostItem = ({
|
||||||
addLike,
|
post: { _id, text, name, avatar, user, likes, comments, date },
|
||||||
removeLike,
|
}) => {
|
||||||
deletePost,
|
const dispatch = useDispatch();
|
||||||
auth,
|
const auth = useSelector((state) => state.auth);
|
||||||
post: { _id, text, name, avatar, user, likes, comments, date }
|
return (
|
||||||
}) => (
|
|
||||||
<div className="post bg-white p-1 my-1">
|
<div className="post bg-white p-1 my-1">
|
||||||
<div>
|
<div>
|
||||||
<Link to={`/profile/${user}`}>
|
<Link to={`/profile/${user}`}>
|
||||||
@@ -23,29 +21,29 @@ const PostItem = ({
|
|||||||
<p className="my-1">{text}</p>
|
<p className="my-1">{text}</p>
|
||||||
<p className="post-date">Posted on {formatDate(date)}</p>
|
<p className="post-date">Posted on {formatDate(date)}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => addLike(_id)}
|
onClick={async () => await dispatch(addLike(_id))}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-light"
|
className="btn btn-light"
|
||||||
>
|
>
|
||||||
<i className="fas fa-thumbs-up" />{' '}
|
<i className="fas fa-thumbs-up" />{" "}
|
||||||
<span>{likes.length > 0 && <span>{likes.length}</span>}</span>
|
<span>{likes.length > 0 && <span>{likes.length}</span>}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeLike(_id)}
|
onClick={async () => await dispatch(removeLike(_id))}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-light"
|
className="btn btn-light"
|
||||||
>
|
>
|
||||||
<i className="fas fa-thumbs-down" />
|
<i className="fas fa-thumbs-down" />
|
||||||
</button>
|
</button>
|
||||||
<Link to={`/posts/${_id}`} className="btn btn-primary">
|
<Link to={`/posts/${_id}`} className="btn btn-primary">
|
||||||
Discussion{' '}
|
Discussion{" "}
|
||||||
{comments.length > 0 && (
|
{comments.length > 0 && (
|
||||||
<span className="comment-count">{comments.length}</span>
|
<span className="comment-count">{comments.length}</span>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{!auth.loading && user === auth.user._id && (
|
{!auth.loading && user === auth.user._id && (
|
||||||
<button
|
<button
|
||||||
onClick={() => deletePost(_id)}
|
onClick={async () => await dispatch(deletePost(_id))}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
>
|
>
|
||||||
@@ -55,20 +53,5 @@ const PostItem = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
PostItem.propTypes = {
|
|
||||||
post: PropTypes.object.isRequired,
|
|
||||||
auth: PropTypes.object.isRequired,
|
|
||||||
addLike: PropTypes.func.isRequired,
|
|
||||||
removeLike: PropTypes.func.isRequired,
|
|
||||||
deletePost: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
export default PostItem;
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
auth: state.auth
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { addLike, removeLike, deletePost })(
|
|
||||||
PostItem
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import PostItem from "./PostItem";
|
||||||
import PostItem from './PostItem';
|
import PostForm from "./PostForm";
|
||||||
import PostForm from './PostForm';
|
import { getPosts } from "../../actions/post";
|
||||||
import { getPosts } from '../../actions/post';
|
|
||||||
|
|
||||||
const Posts = ({ getPosts, post: { posts } }) => {
|
const Posts = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPosts();
|
async function fetchData() {
|
||||||
}, [getPosts]);
|
await dispatch(getPosts());
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch]);
|
||||||
|
const posts = useSelector((state) => state.post.posts);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<section className="container">
|
||||||
@@ -26,13 +31,4 @@ const Posts = ({ getPosts, post: { posts } }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Posts.propTypes = {
|
export default Posts;
|
||||||
getPosts: PropTypes.func.isRequired,
|
|
||||||
post: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
post: state.post
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getPosts })(Posts);
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import { addEducation } from "../../actions/profile";
|
||||||
import { addEducation } from '../../actions/profile';
|
|
||||||
|
|
||||||
const AddEducation = ({ addEducation }) => {
|
const AddEducation = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
school: '',
|
school: "",
|
||||||
degree: '',
|
degree: "",
|
||||||
fieldofstudy: '',
|
fieldofstudy: "",
|
||||||
from: '',
|
from: "",
|
||||||
to: '',
|
to: "",
|
||||||
current: false,
|
current: false,
|
||||||
description: ''
|
description: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { school, degree, fieldofstudy, from, to, description, current } =
|
const { school, degree, fieldofstudy, from, to, description, current } =
|
||||||
@@ -32,9 +32,10 @@ const AddEducation = ({ addEducation }) => {
|
|||||||
<small>* = required field</small>
|
<small>* = required field</small>
|
||||||
<form
|
<form
|
||||||
className="form"
|
className="form"
|
||||||
onSubmit={(e) => {
|
onSubmit={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addEducation(formData).then(() => navigate('/dashboard'));
|
await dispatch(addEducation(formData)).then(() => navigate("/dashboard"));
|
||||||
|
// i have no idea how this works used to work, i removed the navigate function from the addEducation and it does now
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -78,7 +79,7 @@ const AddEducation = ({ addEducation }) => {
|
|||||||
checked={current}
|
checked={current}
|
||||||
value={current}
|
value={current}
|
||||||
onChange={() => setFormData({ ...formData, current: !current })}
|
onChange={() => setFormData({ ...formData, current: !current })}
|
||||||
/>{' '}
|
/>{" "}
|
||||||
Current School
|
Current School
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,8 +112,5 @@ const AddEducation = ({ addEducation }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddEducation.propTypes = {
|
|
||||||
addEducation: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(null, { addEducation })(AddEducation);
|
export default AddEducation;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { addExperience } from '../../actions/profile';
|
import { addExperience } from '../../actions/profile';
|
||||||
|
|
||||||
const AddExperience = ({ addExperience }) => {
|
const AddExperience = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
company: '',
|
company: '',
|
||||||
@@ -31,9 +31,9 @@ const AddExperience = ({ addExperience }) => {
|
|||||||
<small>* = required field</small>
|
<small>* = required field</small>
|
||||||
<form
|
<form
|
||||||
className="form"
|
className="form"
|
||||||
onSubmit={(e) => {
|
onSubmit={async(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addExperience(formData).then(() => navigate('/dashboard'));
|
await dispatch(addExperience(formData)).then(() => navigate('/dashboard'));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -112,8 +112,4 @@ const AddExperience = ({ addExperience }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddExperience.propTypes = {
|
export default AddExperience;
|
||||||
addExperience: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(null, { addExperience })(AddExperience);
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
import React, { Fragment, useState, useEffect } from 'react';
|
import React, { Fragment, useState, useEffect } from "react";
|
||||||
import { Link, useMatch, useNavigate } from 'react-router-dom';
|
import { Link, useMatch, useNavigate } from "react-router-dom";
|
||||||
import PropTypes from 'prop-types';
|
import { createProfile, getCurrentProfile } from "../../actions/profile";
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { createProfile, getCurrentProfile } from '../../actions/profile';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
company: '',
|
company: "",
|
||||||
website: '',
|
website: "",
|
||||||
location: '',
|
location: "",
|
||||||
status: '',
|
status: "",
|
||||||
skills: '',
|
skills: "",
|
||||||
githubusername: '',
|
githubusername: "",
|
||||||
bio: '',
|
bio: "",
|
||||||
twitter: '',
|
twitter: "",
|
||||||
facebook: '',
|
facebook: "",
|
||||||
linkedin: '',
|
linkedin: "",
|
||||||
youtube: '',
|
youtube: "",
|
||||||
instagram: ''
|
instagram: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfileForm = ({
|
const ProfileForm = () => {
|
||||||
profile: { profile, loading },
|
|
||||||
createProfile,
|
const dispatch = useDispatch();
|
||||||
getCurrentProfile
|
|
||||||
}) => {
|
const { profile, loading } = useSelector((state) => state.profile);
|
||||||
|
|
||||||
const [formData, setFormData] = useState(initialState);
|
const [formData, setFormData] = useState(initialState);
|
||||||
|
|
||||||
const creatingProfile = useMatch('/create-profile');
|
const creatingProfile = useMatch("/create-profile");
|
||||||
|
|
||||||
const [displaySocialInputs, toggleSocialInputs] = useState(false);
|
const [displaySocialInputs, toggleSocialInputs] = useState(false);
|
||||||
|
|
||||||
@@ -34,7 +34,10 @@ const ProfileForm = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if there is no profile, attempt to fetch one
|
// if there is no profile, attempt to fetch one
|
||||||
if (!profile) getCurrentProfile();
|
async function fetchData(){
|
||||||
|
await dispatch(getCurrentProfile())
|
||||||
|
}
|
||||||
|
if (!profile) fetchData();
|
||||||
|
|
||||||
// if we finished loading and we do have a profile
|
// if we finished loading and we do have a profile
|
||||||
// then build our profileData
|
// then build our profileData
|
||||||
@@ -48,11 +51,11 @@ const ProfileForm = ({
|
|||||||
}
|
}
|
||||||
// the skills may be an array from our API response
|
// the skills may be an array from our API response
|
||||||
if (Array.isArray(profileData.skills))
|
if (Array.isArray(profileData.skills))
|
||||||
profileData.skills = profileData.skills.join(', ');
|
profileData.skills = profileData.skills.join(", ");
|
||||||
// set local state with the profileData
|
// set local state with the profileData
|
||||||
setFormData(profileData);
|
setFormData(profileData);
|
||||||
}
|
}
|
||||||
}, [loading, getCurrentProfile, profile]);
|
}, [loading, dispatch, profile]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
company,
|
company,
|
||||||
@@ -66,30 +69,31 @@ const ProfileForm = ({
|
|||||||
facebook,
|
facebook,
|
||||||
linkedin,
|
linkedin,
|
||||||
youtube,
|
youtube,
|
||||||
instagram
|
instagram,
|
||||||
} = formData;
|
} = formData;
|
||||||
|
|
||||||
const onChange = (e) =>
|
const onChange = (e) =>
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = async (e) => {
|
||||||
const editing = profile ? true : false;
|
const editing = profile ? true : false;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
createProfile(formData, editing).then(() => {
|
await dispatch(createProfile(formData, editing)).then(() => {
|
||||||
if (!editing) navigate('/dashboard');
|
console.log(editing)
|
||||||
|
if (!editing) navigate("/dashboard");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<section className="container">
|
||||||
<h1 className="large text-primary">
|
<h1 className="large text-primary">
|
||||||
{creatingProfile ? 'Create Your Profile' : 'Edit Your Profile'}
|
{creatingProfile ? "Create Your Profile" : "Edit Your Profile"}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="lead">
|
<p className="lead">
|
||||||
<i className="fas fa-user" />
|
<i className="fas fa-user" />
|
||||||
{creatingProfile
|
{creatingProfile
|
||||||
? ` Let's get some information to make your`
|
? ` Let's get some information to make your`
|
||||||
: ' Add some changes to your profile'}
|
: " Add some changes to your profile"}
|
||||||
</p>
|
</p>
|
||||||
<small>* = required field</small>
|
<small>* = required field</small>
|
||||||
<form className="form" onSubmit={onSubmit}>
|
<form className="form" onSubmit={onSubmit}>
|
||||||
@@ -259,17 +263,4 @@ const ProfileForm = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileForm.propTypes = {
|
export default ProfileForm;
|
||||||
createProfile: PropTypes.func.isRequired,
|
|
||||||
getCurrentProfile: PropTypes.func.isRequired,
|
|
||||||
profile: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
profile: state.profile
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { createProfile, getCurrentProfile })(
|
|
||||||
ProfileForm
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
import React, { Fragment, useEffect } from 'react';
|
import React, { Fragment, useEffect } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import Spinner from "../layout/Spinner";
|
||||||
import Spinner from '../layout/Spinner';
|
import ProfileTop from "./ProfileTop";
|
||||||
import ProfileTop from './ProfileTop';
|
import ProfileAbout from "./ProfileAbout";
|
||||||
import ProfileAbout from './ProfileAbout';
|
import ProfileExperience from "./ProfileExperience";
|
||||||
import ProfileExperience from './ProfileExperience';
|
import ProfileEducation from "./ProfileEducation";
|
||||||
import ProfileEducation from './ProfileEducation';
|
import ProfileGithub from "./ProfileGithub";
|
||||||
import ProfileGithub from './ProfileGithub';
|
import { getProfileById } from "../../actions/profile";
|
||||||
import { getProfileById } from '../../actions/profile';
|
|
||||||
|
|
||||||
const Profile = ({ getProfileById, profile: { profile }, auth }) => {
|
const Profile = () => {
|
||||||
|
const profile = useSelector((state) => state.profile.profile);
|
||||||
|
const auth = useSelector((state) => state.auth);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getProfileById(id);
|
async function fetchData(){
|
||||||
}, [getProfileById, id]);
|
await dispatch(getProfileById(id));
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch,id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<section className="container">
|
||||||
@@ -76,15 +82,5 @@ const Profile = ({ getProfileById, profile: { profile }, auth }) => {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Profile.propTypes = {
|
|
||||||
getProfileById: PropTypes.func.isRequired,
|
|
||||||
profile: PropTypes.object.isRequired,
|
|
||||||
auth: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
export default Profile;
|
||||||
profile: state.profile,
|
|
||||||
auth: state.auth
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getProfileById })(Profile);
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const ProfileAbout = ({
|
const ProfileAbout = ({
|
||||||
profile: {
|
profile: {
|
||||||
@@ -27,8 +26,5 @@ const ProfileAbout = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ProfileAbout.propTypes = {
|
|
||||||
profile: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileAbout;
|
export default ProfileAbout;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import formatDate from '../../utils/formatDate';
|
import formatDate from '../../utils/formatDate';
|
||||||
|
|
||||||
const ProfileEducation = ({
|
const ProfileEducation = ({
|
||||||
@@ -22,9 +21,6 @@ const ProfileEducation = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ProfileEducation.propTypes = {
|
|
||||||
education: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default ProfileEducation;
|
export default ProfileEducation;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import formatDate from '../../utils/formatDate';
|
import formatDate from '../../utils/formatDate';
|
||||||
|
|
||||||
const ProfileExperience = ({
|
const ProfileExperience = ({
|
||||||
@@ -22,9 +21,5 @@ const ProfileExperience = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ProfileExperience.propTypes = {
|
|
||||||
experience: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default ProfileExperience;
|
export default ProfileExperience;
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import { getGithubRepos } from "../../actions/profile";
|
||||||
import { getGithubRepos } from '../../actions/profile';
|
|
||||||
|
|
||||||
const ProfileGithub = ({ username, getGithubRepos, repos }) => {
|
const ProfileGithub = ({ username }) => {
|
||||||
|
const repos = useSelector((state) => state.profile.repos);
|
||||||
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getGithubRepos(username);
|
async function fetchData() {
|
||||||
}, [getGithubRepos, username]);
|
await dispatch(getGithubRepos(username));
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch, username]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile-github">
|
<div className="profile-github">
|
||||||
<h2 className="text-primary my-1">Github Repos</h2>
|
<h2 className="text-primary my-1">Github Repos</h2>
|
||||||
{repos.map(repo => (
|
{repos.map((repo) => (
|
||||||
<div key={repo.id} className="repo bg-white p-1 my-1">
|
<div key={repo.id} className="repo bg-white p-1 my-1">
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
@@ -38,15 +42,4 @@ const ProfileGithub = ({ username, getGithubRepos, repos }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileGithub.propTypes = {
|
export default ProfileGithub;
|
||||||
getGithubRepos: PropTypes.func.isRequired,
|
|
||||||
repos: PropTypes.array.isRequired,
|
|
||||||
username: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
repos: state.profile.repos
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getGithubRepos })(ProfileGithub);
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const ProfileTop = ({
|
const ProfileTop = ({
|
||||||
profile: {
|
profile: {
|
||||||
@@ -8,8 +7,8 @@ const ProfileTop = ({
|
|||||||
location,
|
location,
|
||||||
website,
|
website,
|
||||||
social,
|
social,
|
||||||
user: { name, avatar }
|
user: { name, avatar },
|
||||||
}
|
},
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="profile-top bg-primary p-2">
|
<div className="profile-top bg-primary p-2">
|
||||||
@@ -44,9 +43,4 @@ const ProfileTop = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileTop.propTypes = {
|
|
||||||
profile: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default ProfileTop;
|
export default ProfileTop;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const ProfileItem = ({
|
const ProfileItem = ({
|
||||||
profile: {
|
profile: {
|
||||||
@@ -35,9 +34,5 @@ const ProfileItem = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileItem.propTypes = {
|
|
||||||
profile: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProfileItem;
|
export default ProfileItem;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import React, { Fragment, useEffect } from 'react';
|
import React, { Fragment, useEffect } from "react";
|
||||||
import PropTypes from 'prop-types';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import Spinner from "../layout/Spinner";
|
||||||
import Spinner from '../layout/Spinner';
|
import ProfileItem from "./ProfileItem";
|
||||||
import ProfileItem from './ProfileItem';
|
import { getProfiles } from "../../actions/profile";
|
||||||
import { getProfiles } from '../../actions/profile';
|
|
||||||
|
const Profiles = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const Profiles = ({ getProfiles, profile: { profiles, loading } }) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getProfiles();
|
async function fetchData() {
|
||||||
}, [getProfiles]);
|
await dispatch(getProfiles());
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const { profiles, loading } = useSelector((state) => state.profile);
|
||||||
return (
|
return (
|
||||||
<section className="container">
|
<section className="container">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -36,13 +41,4 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Profiles.propTypes = {
|
export default Profiles;
|
||||||
getProfiles: PropTypes.func.isRequired,
|
|
||||||
profile: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
profile: state.profile
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, { getProfiles })(Profiles);
|
|
||||||
|
|||||||
@@ -1,26 +1,14 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from "react-router-dom";
|
||||||
import PropTypes from 'prop-types';
|
import { useSelector } from "react-redux";
|
||||||
import { connect } from 'react-redux';
|
import Spinner from "../layout/Spinner";
|
||||||
import Spinner from '../layout/Spinner';
|
|
||||||
|
|
||||||
const PrivateRoute = ({
|
const PrivateRoute = ({ component: Component }) => {
|
||||||
component: Component,
|
const { isAuthenticated, loading } = useSelector((state) => state.auth);
|
||||||
auth: { isAuthenticated, loading }
|
|
||||||
}) => {
|
|
||||||
if (loading) return <Spinner />;
|
if (loading) return <Spinner />;
|
||||||
if (isAuthenticated) return <Component />;
|
if (isAuthenticated) return <Component />;
|
||||||
|
|
||||||
return <Navigate to="/login" />;
|
return <Navigate to="/login" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrivateRoute.propTypes = {
|
export default PrivateRoute;
|
||||||
auth: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
|
||||||
auth: state.auth
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(PrivateRoute);
|
|
||||||
|
|||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import {createRoot} from 'react-dom/client'
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'));
|
// Level - 0
|
||||||
|
//Program stars from Here. Imports and renders App.
|
||||||
|
|
||||||
|
const root = createRoot(document.getElementById('root'));
|
||||||
|
root.render(<App/>)
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { SET_ALERT, REMOVE_ALERT } from '../actions/types';
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
const initialState = [];
|
const initialState = [];
|
||||||
|
|
||||||
function alertReducer(state = initialState, action) {
|
const alertSlice = createSlice({
|
||||||
const { type, payload } = action;
|
name: "alert",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setAlert(state, action) {
|
||||||
|
return [...state, action.payload];
|
||||||
|
},
|
||||||
|
removeAlert(state, action) {
|
||||||
|
return state.filter((alert) => alert.id !== action.payload);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
switch (type) {
|
export const { setAlert, removeAlert } = alertSlice.actions;
|
||||||
case SET_ALERT:
|
|
||||||
return [...state, payload];
|
|
||||||
case REMOVE_ALERT:
|
|
||||||
return state.filter((alert) => alert.id !== payload);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default alertSlice.reducer;
|
||||||
export default alertReducer;
|
|
||||||
|
|||||||
+54
-33
@@ -1,55 +1,76 @@
|
|||||||
import {
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
REGISTER_SUCCESS,
|
|
||||||
//REGISTER_FAIL,
|
|
||||||
USER_LOADED,
|
|
||||||
AUTH_ERROR,
|
|
||||||
LOGIN_SUCCESS,
|
|
||||||
//LOGIN_FAIL,
|
|
||||||
LOGOUT,
|
|
||||||
ACCOUNT_DELETED
|
|
||||||
} from '../actions/types';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
token: localStorage.getItem('token'),
|
token: localStorage.getItem("token"),
|
||||||
isAuthenticated: null,
|
isAuthenticated: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
user: null
|
user: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function authReducer(state = initialState, action) {
|
const authSlice = createSlice({
|
||||||
const { type, payload } = action;
|
name: "auth",
|
||||||
|
initialState,
|
||||||
switch (type) {
|
reducers: {
|
||||||
case USER_LOADED:
|
userLoaded(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
user: payload
|
user: action.payload,
|
||||||
};
|
};
|
||||||
case REGISTER_SUCCESS:
|
},
|
||||||
case LOGIN_SUCCESS:
|
registerSuccess(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...payload,
|
...action.payload,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case ACCOUNT_DELETED:
|
},
|
||||||
case AUTH_ERROR:
|
loginSucces(state, action) {
|
||||||
case LOGOUT:
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload,
|
||||||
|
isAuthenticated: true,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
accountDeleted(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
token: null,
|
token: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
user: null
|
user: null,
|
||||||
};
|
};
|
||||||
default:
|
},
|
||||||
return state;
|
authError(state, action) {
|
||||||
}
|
return {
|
||||||
}
|
...state,
|
||||||
|
token: null,
|
||||||
|
isAuthenticated: false,
|
||||||
export default authReducer;
|
loading: false,
|
||||||
|
user: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
logOut(state, action) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
token: null,
|
||||||
|
isAuthenticated: false,
|
||||||
|
loading: false,
|
||||||
|
user: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const {
|
||||||
|
logOut,
|
||||||
|
userLoaded,
|
||||||
|
loginSucces,
|
||||||
|
registerSuccess,
|
||||||
|
accountDeleted,
|
||||||
|
authError,
|
||||||
|
} = authSlice.actions;
|
||||||
|
|
||||||
|
export default authSlice.reducer;
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { combineReducers } from 'redux';
|
|
||||||
import alert from './alert';
|
|
||||||
import auth from './auth';
|
|
||||||
import profile from './profile';
|
|
||||||
import post from './post';
|
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
alert,
|
|
||||||
auth,
|
|
||||||
profile,
|
|
||||||
post
|
|
||||||
|
|
||||||
});
|
|
||||||
+54
-46
@@ -1,84 +1,92 @@
|
|||||||
import {
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
GET_POSTS,
|
|
||||||
POST_ERROR,
|
|
||||||
UPDATE_LIKES,
|
|
||||||
DELETE_POST,
|
|
||||||
ADD_POST,
|
|
||||||
GET_POST,
|
|
||||||
ADD_COMMENT,
|
|
||||||
REMOVE_COMMENT
|
|
||||||
} from '../actions/types';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
posts: [],
|
posts: [],
|
||||||
post: null,
|
post: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
error: {}
|
error: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function postReducer(state = initialState, action) {
|
const postSlice = createSlice({
|
||||||
const { type, payload } = action;
|
name: "post",
|
||||||
|
initialState,
|
||||||
switch (type) {
|
reducers: {
|
||||||
case GET_POSTS:
|
getPostsAction(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
posts: payload,
|
posts: action.payload,
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case GET_POST:
|
},
|
||||||
|
getPostAction(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
post: payload,
|
post: action.payload,
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case ADD_POST:
|
},
|
||||||
|
addPostAction(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
posts: [payload, ...state.posts],
|
posts: [action.payload, ...state.posts],
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case DELETE_POST:
|
},
|
||||||
|
deletePostAction(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
posts: state.posts.filter((post) => post._id !== payload),
|
posts: state.posts.filter((post) => post._id !== action.payload),
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case POST_ERROR:
|
},
|
||||||
|
postError(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: payload,
|
error: action.payload,
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case UPDATE_LIKES:
|
},
|
||||||
|
updateLikes(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
posts: state.posts.map((post) =>
|
posts: state.posts.map((post) =>
|
||||||
post._id === payload.id ? { ...post, likes: payload.likes } : post
|
post._id === action.payload.id
|
||||||
|
? { ...post, likes: action.payload.likes }
|
||||||
|
: post
|
||||||
),
|
),
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case ADD_COMMENT:
|
},
|
||||||
|
addCommentAction(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
post: { ...state.post, comments: payload },
|
post: { ...state.post, comments: action.payload },
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
case REMOVE_COMMENT:
|
},
|
||||||
|
removeComment(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
post: {
|
post: {
|
||||||
...state.post,
|
...state.post,
|
||||||
comments: state.post.comments.filter(
|
comments: state.post.comments.filter(
|
||||||
(comment) => comment._id !== payload
|
(comment) => comment._id !== action.payload
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
loading: false
|
loading: false,
|
||||||
};
|
};
|
||||||
default:
|
},
|
||||||
return state;
|
},
|
||||||
}
|
});
|
||||||
}
|
export const {
|
||||||
|
removeComment,
|
||||||
export default postReducer;
|
addCommentAction,
|
||||||
|
updateLikes,
|
||||||
|
postError,
|
||||||
|
deletePostAction,
|
||||||
|
getPostAction,
|
||||||
|
getPostsAction,
|
||||||
|
addPostAction,
|
||||||
|
} = postSlice.actions;
|
||||||
|
|
||||||
|
export default postSlice.reducer;
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import {
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
GET_PROFILE,
|
|
||||||
PROFILE_ERROR,
|
|
||||||
CLEAR_PROFILE,
|
|
||||||
UPDATE_PROFILE,
|
|
||||||
GET_PROFILES,
|
|
||||||
GET_REPOS,
|
|
||||||
NO_REPOS
|
|
||||||
} from '../actions/types';
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
profile: null,
|
profile: null,
|
||||||
@@ -14,54 +6,70 @@ const initialState = {
|
|||||||
repos: [],
|
repos: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
error: {},
|
error: {},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
const profileSlice = createSlice({
|
||||||
function profileReducer(state = initialState, action) {
|
name: "profile",
|
||||||
const { type, payload } = action;
|
initialState,
|
||||||
|
reducers: {
|
||||||
switch (type) {
|
getProfile(state, action) {
|
||||||
case GET_PROFILE:
|
|
||||||
case UPDATE_PROFILE:
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
profile: payload,
|
profile: action.payload,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
case GET_PROFILES:
|
},
|
||||||
|
updateProfile(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
profiles: payload,
|
profile: action.payload,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
case PROFILE_ERROR:
|
},
|
||||||
|
getProfilesType(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: payload,
|
profiles: action.payload,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
profileError(state, action) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: action.payload,
|
||||||
loading: false,
|
loading: false,
|
||||||
profile: null
|
|
||||||
profile: null,
|
profile: null,
|
||||||
};
|
};
|
||||||
case CLEAR_PROFILE:
|
},
|
||||||
|
clearProfile(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
profile: null,
|
profile: null,
|
||||||
repos: []
|
repos: [],
|
||||||
};
|
};
|
||||||
case GET_REPOS:
|
},
|
||||||
|
getRepos(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
repos: payload,
|
repos: action.payload,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
case NO_REPOS:
|
},
|
||||||
|
noRepos(state, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
repos: [],
|
repos: [],
|
||||||
};
|
};
|
||||||
default:
|
},
|
||||||
return state;
|
},
|
||||||
}
|
});
|
||||||
}
|
export const {
|
||||||
|
noRepos,
|
||||||
|
getRepos,
|
||||||
|
clearProfile,
|
||||||
|
profileError,
|
||||||
|
getProfilesType,
|
||||||
|
updateProfile,
|
||||||
|
getProfile,
|
||||||
|
} = profileSlice.actions;
|
||||||
|
|
||||||
export default profileReducer;
|
export default profileSlice.reducer;
|
||||||
|
|||||||
+12
-20
@@ -1,28 +1,20 @@
|
|||||||
import { createStore, applyMiddleware } from 'redux';
|
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
|
||||||
import thunk from 'redux-thunk';
|
|
||||||
import rootReducer from './reducers';
|
|
||||||
import setAuthToken from './utils/setAuthToken';
|
import setAuthToken from './utils/setAuthToken';
|
||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = {};
|
import alertReducer from './reducers/alert';
|
||||||
|
import authReducer from './reducers/auth';
|
||||||
|
import profileReducer from './reducers/profile'
|
||||||
|
import postReducer from './reducers/post'
|
||||||
|
|
||||||
const middleware = [thunk];
|
|
||||||
|
|
||||||
const store = createStore(
|
const store = configureStore({
|
||||||
rootReducer,
|
reducer: {
|
||||||
initialState,
|
alert: alertReducer,
|
||||||
composeWithDevTools(applyMiddleware(...middleware))
|
auth: authReducer,
|
||||||
);
|
profile: profileReducer,
|
||||||
|
post: postReducer
|
||||||
|
}})
|
||||||
|
|
||||||
/*
|
|
||||||
NOTE: set up a store subscription listener
|
|
||||||
to store the users token in localStorage
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
initialize current state from redux store for subscription comparison
|
|
||||||
preventing undefined error
|
|
||||||
*/
|
|
||||||
let currentState = store.getState();
|
let currentState = store.getState();
|
||||||
|
|
||||||
store.subscribe(() => {
|
store.subscribe(() => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
import { LOGOUT } from '../actions/types';
|
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
@@ -12,7 +11,7 @@ api.interceptors.response.use(
|
|||||||
(res) => res,
|
(res) => res,
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err.response.status === 401) {
|
if (err.response.status === 401) {
|
||||||
store.dispatch({ type: LOGOUT });
|
store.dispatch({ type: 'auth/logOut' });
|
||||||
}
|
}
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+29276
-1158
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -7,25 +7,25 @@
|
|||||||
"start": "node server",
|
"start": "node server",
|
||||||
"server": "nodemon server",
|
"server": "nodemon server",
|
||||||
"client": "npm start --prefix client --trace-depracation",
|
"client": "npm start --prefix client --trace-depracation",
|
||||||
"dev": "concurrently \"npm run server\" \"npm run client\""
|
"dev": "concurrently \"npm run server\" \"npm run client\"",
|
||||||
|
"render": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.1.3",
|
"axios": "^0.21.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"config": "^3.3.8",
|
"client": "file:client",
|
||||||
"express": "^4.18.2",
|
"config": "^3.3.3",
|
||||||
"express-validator": "^6.14.2",
|
"express": "^4.17.1",
|
||||||
"gravatar": "^1.8.2",
|
"express-validator": "^6.8.1",
|
||||||
|
"gravatar": "^1.8.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mongoose": "^6.6.5",
|
"mongoose": "^5.11.8",
|
||||||
"normalize-url": "^5.0.0",
|
"normalize-url": "^5.3.0"
|
||||||
"request": "^2.88.2",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^7.4.0",
|
"concurrently": "^5.3.0",
|
||||||
"nodemon": "^2.0.20"
|
"nodemon": "^2.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ app.use('/api/profile', require('./routers/api/profile'))
|
|||||||
app.use('/api/posts', require('./routers/api/posts'))
|
app.use('/api/posts', require('./routers/api/posts'))
|
||||||
|
|
||||||
// Serve static assets in production
|
// Serve static assets in production
|
||||||
|
|
||||||
if (process.env.NODE_ENV==='production'){
|
if (process.env.NODE_ENV==='production'){
|
||||||
app.use(express.static('client/build'));
|
app.use(express.static('client/build'));
|
||||||
app.get('*',(req, res)=>[
|
app.get('*',(req, res)=>[
|
||||||
@@ -22,6 +23,7 @@ if (process.env.NODE_ENV==='production'){
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 5000;
|
const PORT = process.env.PORT || 5000;
|
||||||
|
|
||||||
app.listen(PORT,()=> console.log(`Server started on port ${PORT}`));
|
app.listen(PORT,()=> console.log(`Server started on port ${PORT}`));
|
||||||
|
|||||||
Reference in New Issue
Block a user