Merge pull request #1 from QkoSad/rtk

Rtk
This commit is contained in:
Andrean
2023-06-07 09:26:03 +00:00
committed by GitHub
53 changed files with 36830 additions and 7676 deletions
+1
View File
@@ -1,3 +1,4 @@
node_modules
default.json
.vscode
TODO.txt
+23
View File
@@ -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*
+6734 -5503
View File
File diff suppressed because it is too large Load Diff
+7 -11
View File
@@ -1,26 +1,22 @@
{
"name": "client",
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.1.3",
"moment": "^2.29.4",
"axios": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-moment": "^1.1.2",
"react-redux": "^8.0.4",
"react-router-dom": "^6.4.2",
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.2",
"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"
},
"scripts": {
"start": "react-scripts start --openssl-legacy-provider start",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
@@ -43,5 +39,5 @@
"last 1 safari version"
]
},
"proxy":"http://localhost:5000"
"proxy": "http://localhost:5000"
}
+31 -3
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<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="theme-color" content="#000000" />
<link
@@ -11,11 +11,39 @@
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
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" />
<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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<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>
</html>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+11 -1
View File
@@ -6,10 +6,20 @@
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+1
View File
@@ -1,3 +1,4 @@
/* Global Styles */
:root {
--primary-color: #17a2b8;
+16 -2
View File
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Landing from './components/layout/Landing';
import Register from './components/auth/Register';
@@ -15,7 +16,6 @@ import Posts from './components/posts/Posts';
import Post from './components/post/Post';
import NotFound from './components/layout/NotFound';
import PrivateRoute from './components/routing/PrivateRoute';
import { LOGOUT } from './actions/types';
// Redux
import { Provider } from 'react-redux';
@@ -24,6 +24,10 @@ import { loadUser } from './actions/auth';
import setAuthToken from './utils/setAuthToken';
import './App.css';
import { logOut } from './reducers/auth';
// Level - 1
//
const App = () => {
useEffect(() => {
@@ -38,7 +42,7 @@ const App = () => {
// log user out from all tabs if they log out in one tab
window.addEventListener("storage", () => {
if (!localStorage.token) store.dispatch({ type: LOGOUT });
if (!localStorage.token) store.dispatch(logOut);
});
}, []);
@@ -80,6 +84,16 @@ const App = () => {
</Router>
</Provider>
);
/*
return (
<Provider store={store}>
<Router>
<Routes>
<Route path="login" element={<Login />} />
</Routes>
</Router>
</Provider>
);*/
};
export default App;
+4 -7
View File
@@ -1,12 +1,9 @@
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();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id }
});
dispatch(setAlert({ msg, alertType, id }));
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
setTimeout(() => dispatch(removeAlert(id)), timeout);
};
+33 -51
View File
@@ -1,15 +1,12 @@
import api from "../utils/api";
import { setAlert } from "./alert";
import { createAlert } from "./alert";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
} from "./types";
loginSucces,
authError,
registerSuccess,
logOut,
userLoaded,
} 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
@@ -17,19 +14,32 @@ import {
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) => {
try {
const res = await api.get("/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
dispatch(userLoaded(res.data));
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
dispatch(authError());
}
};
@@ -38,49 +48,21 @@ export const register = (formData) => async (dispatch) => {
try {
const res = await api.post("/users", formData);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(registerSuccess(res.data));
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, "danger")));
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
}
dispatch({
type: REGISTER_FAIL,
type: "auth/registerFail",
});
}
};
// Login User
export const login = (email, password) => async (dispatch) => {
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,
});
}
export const logout = () => async (dispatch) => {
dispatch(logOut);
};
// Logout
export const logout = () => ({ type: LOGOUT });
+50 -89
View File
@@ -1,37 +1,26 @@
import api from '../utils/api';
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR,
UPDATE_LIKES,
DELETE_POST,
ADD_POST,
GET_POST,
ADD_COMMENT,
REMOVE_COMMENT
} from './types';
/*
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
*/
removeComment,
addCommentAction,
updateLikes,
postError,
deletePostAction,
getPostAction,
getPostsAction,
addPostAction,
} from "../reducers/post";
import api from "../utils/api";
import { createAlert } from "./alert";
// Get posts
export const getPosts = () => async (dispatch) => {
try {
const res = await api.get('/posts');
const res = await api.get("/posts");
dispatch({
type: GET_POSTS,
payload: res.data
});
dispatch(getPostsAction(res.data));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -40,15 +29,11 @@ export const addLike = (id) => async (dispatch) => {
try {
const res = await api.put(`/posts/like/${id}`);
dispatch({
type: UPDATE_LIKES,
payload: { id, likes: res.data }
});
dispatch(updateLikes({ id, likes: res.data }));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -57,15 +42,11 @@ export const removeLike = (id) => async (dispatch) => {
try {
const res = await api.put(`/posts/unlike/${id}`);
dispatch({
type: UPDATE_LIKES,
payload: { id, likes: res.data }
});
dispatch(updateLikes({ id, likes: res.data }));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -74,36 +55,28 @@ export const deletePost = (id) => async (dispatch) => {
try {
await api.delete(`/posts/${id}`);
dispatch({
type: DELETE_POST,
payload: id
});
dispatch(deletePostAction(id));
dispatch(setAlert('Post Removed', 'success'));
dispatch(createAlert("Post Removed", "success"));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
// Add post
export const addPost = (formData) => async (dispatch) => {
try {
const res = await api.post('/posts', formData);
const res = await api.post("/posts", formData);
dispatch({
type: ADD_POST,
payload: res.data
});
dispatch(addPostAction(res.data));
dispatch(setAlert('Post Created', 'success'));
dispatch(createAlert("Post Created", "success"));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -112,15 +85,11 @@ export const getPost = (id) => async (dispatch) => {
try {
const res = await api.get(`/posts/${id}`);
dispatch({
type: GET_POST,
payload: res.data
});
dispatch(getPostAction(res.data));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -129,17 +98,13 @@ export const addComment = (postId, formData) => async (dispatch) => {
try {
const res = await api.post(`/posts/comment/${postId}`, formData);
dispatch({
type: ADD_COMMENT,
payload: res.data
});
dispatch(addCommentAction(res.data));
dispatch(setAlert('Comment Added', 'success'));
dispatch(createAlert("Comment Added", "success"));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
@@ -148,16 +113,12 @@ export const deleteComment = (postId, commentId) => async (dispatch) => {
try {
await api.delete(`/posts/comment/${postId}/${commentId}`);
dispatch({
type: REMOVE_COMMENT,
payload: commentId
});
dispatch(removeComment(commentId));
dispatch(setAlert('Comment Removed', 'success'));
dispatch(createAlert("Comment Removed", "success"));
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
postError({ msg: err.response.statusText, status: err.response.status })
);
}
};
};
+98 -121
View File
@@ -1,57 +1,48 @@
import api from '../utils/api';
import { setAlert } from './alert';
import api from "../utils/api";
import { createAlert } from "./alert";
import {
GET_PROFILE,
GET_PROFILES,
PROFILE_ERROR,
UPDATE_PROFILE,
CLEAR_PROFILE,
ACCOUNT_DELETED,
GET_REPOS,
NO_REPOS
} from './types';
noRepos,
getRepos,
clearProfile,
profileError,
getProfilesType,
updateProfile,
getProfile,
} from "../reducers/profile";
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
export const getCurrentProfile = () => async (dispatch) => {
try {
const res = await api.get('/profile/me');
const res = await api.get("/profile/me");
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(getProfile(res.data));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
// Get all profiles
export const getProfiles = () => async (dispatch) => {
dispatch({ type: CLEAR_PROFILE });
dispatch(clearProfile());
try {
const res = await api.get('/profile');
const res = await api.get("/profile");
dispatch({
type: GET_PROFILES,
payload: res.data
});
dispatch(getProfilesType(res.data));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
@@ -60,15 +51,14 @@ export const getProfileById = (userId) => async (dispatch) => {
try {
const res = await api.get(`/profile/user/${userId}`);
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(getProfile(res.data));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
@@ -77,101 +67,88 @@ export const getGithubRepos = (username) => async (dispatch) => {
try {
const res = await api.get(`/profile/github/${username}`);
dispatch({
type: GET_REPOS,
payload: res.data
});
dispatch(getRepos(res.data));
} catch (err) {
dispatch({
type: NO_REPOS
});
dispatch(noRepos());
}
};
// Create or update profile
export const createProfile =
(formData, navigate, edit = false) =>
(formData, edit = false) =>
async (dispatch) => {
try {
const res = await api.post('/profile', formData);
const res = await api.post("/profile", formData);
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(getProfile(res.data));
dispatch(
setAlert(edit ? 'Profile Updated' : 'Profile Created', 'success')
createAlert(edit ? "Profile Updated" : "Profile Created", "success")
);
if (!edit) {
navigate('/dashboard');
}
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
// Add Experience
export const addExperience = (formData, navigate) => async (dispatch) => {
export const addExperience = (formData) => async (dispatch) => {
try {
const res = await api.put('/profile/experience', formData);
const res = await api.put("/profile/experience", formData);
dispatch({
type: UPDATE_PROFILE,
payload: res.data
});
dispatch(updateProfile(res.data));
dispatch(setAlert('Experience Added', 'success'));
dispatch(createAlert("Experience Added", "success"));
navigate('/dashboard');
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
// Add Education
export const addEducation = (formData, navigate) => async (dispatch) => {
export const addEducation = (formData) => async (dispatch) => {
try {
const res = await api.put('/profile/education', formData);
const res = await api.put("/profile/education", formData);
dispatch({
type: UPDATE_PROFILE,
payload: res.data
});
dispatch(updateProfile(res.data));
dispatch(setAlert('Education Added', 'success'));
dispatch(createAlert("Education Added", "success"));
navigate('/dashboard');
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
errors.forEach((error) => dispatch(createAlert(error.msg, "danger")));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
@@ -180,17 +157,16 @@ export const deleteExperience = (id) => async (dispatch) => {
try {
const res = await api.delete(`/profile/experience/${id}`);
dispatch({
type: UPDATE_PROFILE,
payload: res.data
});
dispatch(updateProfile(res.data));
dispatch(setAlert('Experience Removed', 'success'));
dispatch(createAlert("Experience Removed", "success"));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
@@ -199,35 +175,36 @@ export const deleteEducation = (id) => async (dispatch) => {
try {
const res = await api.delete(`/profile/education/${id}`);
dispatch({
type: UPDATE_PROFILE,
payload: res.data
});
dispatch(updateProfile(res.data));
dispatch(setAlert('Education Removed', 'success'));
dispatch(createAlert("Education Removed", "success"));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
};
// Delete account & profile
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 {
await api.delete('/profile');
await api.delete("/profile");
dispatch({ type: CLEAR_PROFILE });
dispatch({ type: ACCOUNT_DELETED });
dispatch(clearProfile());
dispatch(accountDeleted());
dispatch(setAlert('Your account has been permanently deleted'));
dispatch(createAlert("Your account has been permanently deleted"));
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
}
}
};
};
-25
View File
@@ -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';
+12 -22
View File
@@ -1,23 +1,22 @@
import React, { useState } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { login } from '../../actions/auth';
import React, { useState } from "react";
import { Link, Navigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { login } from "../../actions/auth";
const Login = ({ login, isAuthenticated }) => {
const Login = () => {
const [formData, setFormData] = useState({
email: '',
password: ''
email: "",
password: "",
});
const dispatch = useDispatch();
const { email, password } = formData;
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const onChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = (e) => {
const onSubmit = async (e) => {
e.preventDefault();
login(email, password);
await dispatch(login(email, password));
};
if (isAuthenticated) {
@@ -59,13 +58,4 @@ const Login = ({ login, isAuthenticated }) => {
);
};
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps, { login })(Login);
export default Login;
+9 -18
View File
@@ -1,18 +1,19 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { useDispatch,useSelector } from 'react-redux';
import { Link, Navigate } from 'react-router-dom';
import { setAlert } from '../../actions/alert';
import { createAlert } from '../../actions/alert';
import { register } from '../../actions/auth';
import PropTypes from 'prop-types';
const Register = ({ setAlert, register, isAuthenticated }) => {
const Register = () => {
const dispatch = useDispatch();
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
password2: ''
});
const isAuthenticated = useSelector(state=>state.auth.isAuthenticated)
const { name, email, password, password2 } = formData;
const onChange = (e) =>
@@ -21,9 +22,9 @@ const Register = ({ setAlert, register, isAuthenticated }) => {
const onSubmit = async (e) => {
e.preventDefault();
if (password !== password2) {
setAlert('Passwords do not match', 'danger');
await dispatch(createAlert('Passwords do not match', 'danger'));
} else {
register({ name, email, password });
await dispatch(register({ name, email, password }));
}
};
@@ -87,14 +88,4 @@ const Register = ({ setAlert, register, isAuthenticated }) => {
);
};
Register.propTypes = {
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);
export default Register;
+22 -36
View File
@@ -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';
import { Link } from 'react-router-dom';
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 }
}) => {
const Dashboard = () => {
const dispatch = useDispatch();
useEffect(() => {
getCurrentProfile();
}, [getCurrentProfile]);
function fetchData() {
dispatch(getCurrentProfile());
}
fetchData();
}, [dispatch]);
const user = useSelector((state) => state.auth.user);
const profile = useSelector((state) => state.profile.profile);
return (
<section className="container">
<h1 className="large text-primary">Dashboard</h1>
@@ -32,8 +29,11 @@ const Dashboard = ({
<Education education={profile.education} />
<div className="my-2">
<button className="btn btn-danger" onClick={() => deleteAccount()}>
<i className="fas fa-user-minus" /> Delete My Account
<button
className="btn btn-danger"
onClick={async () => await dispatch(deleteAccount())}
>
<i className="fas fa-user" /> Delete My Account
</button>
</div>
</>
@@ -49,18 +49,4 @@ const Dashboard = ({
);
};
Dashboard.propTypes = {
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
);
export default Dashboard;
+9 -14
View File
@@ -1,20 +1,20 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { deleteEducation } from '../../actions/profile';
import formatDate from '../../utils/formatDate';
import React, { Fragment } from "react";
import {useDispatch } from "react-redux";
import { deleteEducation } from "../../actions/profile";
import formatDate from "../../utils/formatDate";
const Education = ({ education, deleteEducation }) => {
const Education = ({education}) => {
const dispatch = useDispatch();
const educations = education.map((edu) => (
<tr key={edu._id}>
<td>{edu.school}</td>
<td className="hide-sm">{edu.degree}</td>
<td>
{formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : 'Now'}
{formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : "Now"}
</td>
<td>
<button
onClick={() => deleteEducation(edu._id)}
onClick={async () => await dispatch(deleteEducation(edu._id))}
className="btn btn-danger"
>
Delete
@@ -41,9 +41,4 @@ const Education = ({ education, deleteEducation }) => {
);
};
Education.propTypes = {
education: PropTypes.array.isRequired,
deleteEducation: PropTypes.func.isRequired
};
export default connect(null, { deleteEducation })(Education);
export default Education;
@@ -1,10 +1,10 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {useDispatch } from 'react-redux';
import { deleteExperience } from '../../actions/profile';
import formatDate from '../../utils/formatDate';
const Experience = ({ experience, deleteExperience }) => {
const Experience = ({experience}) => {
const dispatch = useDispatch();
const experiences = experience.map((exp) => (
<tr key={exp._id}>
<td>{exp.company}</td>
@@ -14,7 +14,7 @@ const Experience = ({ experience, deleteExperience }) => {
</td>
<td>
<button
onClick={() => deleteExperience(exp._id)}
onClick={async () => dispatch(deleteExperience(exp._id))}
className="btn btn-danger"
>
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;
+14 -21
View File
@@ -1,23 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const Alert = ({ alerts }) => (
<div className="alert-wrapper">
{alerts.map((alert) => (
<div key={alert.id} className={`alert alert-${alert.alertType}`}>
{alert.msg}
</div>
))}
</div>
);
Alert.propTypes = {
alerts: PropTypes.array.isRequired
import React from "react";
import { useSelector } from "react-redux";
const Alert = () => {
const alerts = useSelector((state) => state.alert);
return (
<div className="alert-wrapper">
{alerts.map((alert) => (
<div key={alert.id} className={`alert alert-${alert.alertType}`}>
{alert.msg}
</div>
))}
</div>
);
};
const mapStateToProps = (state) => ({
alerts: state.alert
});
export default connect(mapStateToProps)(Alert);
export default Alert;
+6 -15
View File
@@ -1,9 +1,9 @@
import React from 'react';
import { Link, Navigate } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import React from "react";
import { Link, Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
const Landing = ({ isAuthenticated }) => {
const Landing = () => {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
if (isAuthenticated) {
return <Navigate to="/dashboard" />;
}
@@ -31,13 +31,4 @@ const Landing = ({ isAuthenticated }) => {
);
};
Landing.propTypes = {
isAuthenticated: PropTypes.bool
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(Landing);
export default Landing;
+11 -20
View File
@@ -1,10 +1,11 @@
import React, { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { logout } from '../../actions/auth';
import React, { Fragment } from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { logOut } from "../../reducers/auth";
const Navbar = ({ auth: { isAuthenticated }, logout }) => {
const Navbar = () => {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const authLinks = (
<ul>
<li>
@@ -15,12 +16,11 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
</li>
<li>
<Link to="/dashboard">
<i className="fas fa-user" />{' '}
<span className="hide-sm">Dashboard</span>
<i className="fas fa-user" /> <span className="hide-sm">Profile</span>
</Link>
</li>
<li>
<a onClick={logout} href="#!">
<a onClick={()=>dispatch(logOut())} href="#!">
<i className="fas fa-sign-out-alt" />{' '}
<span className="hide-sm">Logout</span>
</a>
@@ -45,7 +45,7 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
return (
<nav className="navbar bg-dark">
<h1>
<Link to="/">
<Link to="/posts">
<i className="fas fa-code" /> DevConnector
</Link>
</h1>
@@ -54,13 +54,4 @@ const Navbar = ({ auth: { isAuthenticated }, logout }) => {
);
};
Navbar.propTypes = {
logout: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps, { logout })(Navbar);
export default Navbar;
+6 -12
View File
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { useDispatch } from 'react-redux';
import { addComment } from '../../actions/post';
const CommentForm = ({ postId, addComment }) => {
const CommentForm = ({ postId }) => {
const [text, setText] = useState('');
const dispatch = useDispatch();
return (
<div className='post-form'>
@@ -13,9 +13,9 @@ const CommentForm = ({ postId, addComment }) => {
</div>
<form
className='form my-1'
onSubmit={e => {
onSubmit={async(e) => {
e.preventDefault();
addComment(postId, { text });
await dispatch(addComment(postId, { text }));
setText('');
}}
>
@@ -34,12 +34,6 @@ const CommentForm = ({ postId, addComment }) => {
);
};
CommentForm.propTypes = {
addComment: PropTypes.func.isRequired
};
export default connect(
null,
{ addComment }
)(CommentForm);
export default CommentForm
+31 -42
View File
@@ -1,49 +1,38 @@
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import formatDate from '../../utils/formatDate';
import { deleteComment } from '../../actions/post';
import React from "react";
import { Link } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import formatDate from "../../utils/formatDate";
import { deleteComment } from "../../actions/post";
const CommentItem = ({
postId,
comment: { _id, text, name, avatar, user, date },
auth,
deleteComment
}) => (
<div className="post bg-white p-1 my-1">
<div>
<Link to={`/profile/${user}`}>
<img className="round-img" src={avatar} alt="" />
<h4>{name}</h4>
</Link>
}) => {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
return (
<div className="post bg-white p-1 my-1">
<div>
<Link to={`/profile/${user}`}>
<img className="round-img" src={avatar} alt="" />
<h4>{name}</h4>
</Link>
</div>
<div>
<p className="my-1">{text}</p>
<p className="post-date">Posted on {formatDate(date)}</p>
{!auth.loading && user === auth.user._id && (
<button
onClick={async () => await dispatch(deleteComment(postId, _id))}
type="button"
className="btn btn-danger"
>
<i className="fas fa-times" />
</button>
)}
</div>
</div>
<div>
<p className="my-1">{text}</p>
<p className="post-date">Posted on {formatDate(date)}</p>
{!auth.loading && user === auth.user._id && (
<button
onClick={() => deleteComment(postId, _id)}
type="button"
className="btn btn-danger"
>
<i className="fas fa-times" />
</button>
)}
</div>
</div>
);
CommentItem.propTypes = {
postId: PropTypes.string.isRequired,
comment: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
deleteComment: PropTypes.func.isRequired
);
};
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps, { deleteComment })(CommentItem);
export default CommentItem;
+17 -23
View File
@@ -1,18 +1,22 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Link, useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from '../posts/PostItem';
import CommentForm from '../post/CommentForm';
import CommentItem from '../post/CommentItem';
import { getPost } from '../../actions/post';
import React, { useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import Spinner from "../layout/Spinner";
import PostItem from "../posts/PostItem";
import CommentForm from "../post/CommentForm";
import CommentItem from "../post/CommentItem";
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();
useEffect(() => {
getPost(id);
}, [getPost, id]);
async function fetchData() {
await dispatch(getPost(id));
}
fetchData();
}, [dispatch, id]);
return loading || post === null ? (
<Spinner />
@@ -32,14 +36,4 @@ const Post = ({ getPost, post: { post, loading } }) => {
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
post: state.post
});
export default connect(mapStateToProps, { getPost })(Post);
export default Post;
+19 -27
View File
@@ -1,44 +1,36 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addPost } from '../../actions/post';
const PostForm = ({ addPost }) => {
const [text, setText] = useState('');
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { addPost } from "../../actions/post";
const PostForm = () => {
const [text, setText] = useState("");
const dispatch = useDispatch();
return (
<div className='post-form'>
<div className='bg-primary p'>
<div className="post-form">
<div className="bg-primary p">
<h3>Say Something...</h3>
</div>
<form
className='form my-1'
onSubmit={e => {
className="form my-1"
onSubmit={async (e) => {
e.preventDefault();
addPost({ text });
setText('');
await dispatch(addPost({ text }));
setText("");
}}
>
<textarea
name='text'
cols='30'
rows='5'
placeholder='Create a post'
name="text"
cols="30"
rows="5"
placeholder="Create a post"
value={text}
onChange={e => setText(e.target.value)}
onChange={(e) => setText(e.target.value)}
required
/>
<input type='submit' className='btn btn-dark my-1' value='Submit' />
<input type="submit" className="btn btn-dark my-1" value="Submit" />
</form>
</div>
);
};
PostForm.propTypes = {
addPost: PropTypes.func.isRequired
};
export default connect(
null,
{ addPost }
)(PostForm);
export default PostForm;
+49 -66
View File
@@ -1,74 +1,57 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import formatDate from '../../utils/formatDate';
import { connect } from 'react-redux';
import { addLike, removeLike, deletePost } from '../../actions/post';
import React from "react";
import { Link } from "react-router-dom";
import formatDate from "../../utils/formatDate";
import { useDispatch, useSelector } from "react-redux";
import { addLike, removeLike, deletePost } from "../../actions/post";
const PostItem = ({
addLike,
removeLike,
deletePost,
auth,
post: { _id, text, name, avatar, user, likes, comments, date }
}) => (
<div className="post bg-white p-1 my-1">
<div>
<Link to={`/profile/${user}`}>
<img className="round-img" src={avatar} alt="" />
<h4>{name}</h4>
</Link>
</div>
<div>
<p className="my-1">{text}</p>
<p className="post-date">Posted on {formatDate(date)}</p>
<button
onClick={() => addLike(_id)}
type="button"
className="btn btn-light"
>
<i className="fas fa-thumbs-up" />{' '}
<span>{likes.length > 0 && <span>{likes.length}</span>}</span>
</button>
<button
onClick={() => removeLike(_id)}
type="button"
className="btn btn-light"
>
<i className="fas fa-thumbs-down" />
</button>
<Link to={`/posts/${_id}`} className="btn btn-primary">
Discussion{' '}
{comments.length > 0 && (
<span className="comment-count">{comments.length}</span>
)}
</Link>
{!auth.loading && user === auth.user._id && (
post: { _id, text, name, avatar, user, likes, comments, date },
}) => {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
return (
<div className="post bg-white p-1 my-1">
<div>
<Link to={`/profile/${user}`}>
<img className="round-img" src={avatar} alt="" />
<h4>{name}</h4>
</Link>
</div>
<div>
<p className="my-1">{text}</p>
<p className="post-date">Posted on {formatDate(date)}</p>
<button
onClick={() => deletePost(_id)}
onClick={async () => await dispatch(addLike(_id))}
type="button"
className="btn btn-danger"
className="btn btn-light"
>
<i className="fas fa-times" />
<i className="fas fa-thumbs-up" />{" "}
<span>{likes.length > 0 && <span>{likes.length}</span>}</span>
</button>
)}
<button
onClick={async () => await dispatch(removeLike(_id))}
type="button"
className="btn btn-light"
>
<i className="fas fa-thumbs-down" />
</button>
<Link to={`/posts/${_id}`} className="btn btn-primary">
Discussion{" "}
{comments.length > 0 && (
<span className="comment-count">{comments.length}</span>
)}
</Link>
{!auth.loading && user === auth.user._id && (
<button
onClick={async () => await dispatch(deletePost(_id))}
type="button"
className="btn btn-danger"
>
<i className="fas fa-times" />
</button>
)}
</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
);
};
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps, { addLike, removeLike, deletePost })(
PostItem
);
export default PostItem;
+15 -19
View File
@@ -1,14 +1,19 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import PostItem from './PostItem';
import PostForm from './PostForm';
import { getPosts } from '../../actions/post';
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import PostItem from "./PostItem";
import PostForm from "./PostForm";
import { getPosts } from "../../actions/post";
const Posts = ({ getPosts, post: { posts } }) => {
const Posts = () => {
const dispatch = useDispatch();
useEffect(() => {
getPosts();
}, [getPosts]);
async function fetchData() {
await dispatch(getPosts());
}
fetchData();
}, [dispatch]);
const posts = useSelector((state) => state.post.posts);
return (
<section className="container">
@@ -26,13 +31,4 @@ const Posts = ({ getPosts, post: { posts } }) => {
);
};
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts);
export default Posts;
@@ -1,19 +1,19 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addEducation } from '../../actions/profile';
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { addEducation } from "../../actions/profile";
const AddEducation = ({ addEducation }) => {
const AddEducation = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [formData, setFormData] = useState({
school: '',
degree: '',
fieldofstudy: '',
from: '',
to: '',
school: "",
degree: "",
fieldofstudy: "",
from: "",
to: "",
current: false,
description: ''
description: "",
});
const { school, degree, fieldofstudy, from, to, description, current } =
@@ -32,9 +32,10 @@ const AddEducation = ({ addEducation }) => {
<small>* = required field</small>
<form
className="form"
onSubmit={(e) => {
onSubmit={async (e) => {
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">
@@ -78,7 +79,7 @@ const AddEducation = ({ addEducation }) => {
checked={current}
value={current}
onChange={() => setFormData({ ...formData, current: !current })}
/>{' '}
/>{" "}
Current School
</p>
</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 { Link, useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { useDispatch } from 'react-redux';
import { addExperience } from '../../actions/profile';
const AddExperience = ({ addExperience }) => {
const AddExperience = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [formData, setFormData] = useState({
company: '',
@@ -31,9 +31,9 @@ const AddExperience = ({ addExperience }) => {
<small>* = required field</small>
<form
className="form"
onSubmit={(e) => {
onSubmit={async(e) => {
e.preventDefault();
addExperience(formData).then(() => navigate('/dashboard'));
await dispatch(addExperience(formData)).then(() => navigate('/dashboard'));
}}
>
<div className="form-group">
@@ -112,8 +112,4 @@ const AddExperience = ({ addExperience }) => {
);
};
AddExperience.propTypes = {
addExperience: PropTypes.func.isRequired
};
export default connect(null, { addExperience })(AddExperience);
export default AddExperience;
@@ -1,32 +1,32 @@
import React, { Fragment, useState, useEffect } from 'react';
import { Link, useMatch, useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createProfile, getCurrentProfile } from '../../actions/profile';
import React, { Fragment, useState, useEffect } from "react";
import { Link, useMatch, useNavigate } from "react-router-dom";
import { createProfile, getCurrentProfile } from "../../actions/profile";
import { useDispatch, useSelector } from "react-redux";
const initialState = {
company: '',
website: '',
location: '',
status: '',
skills: '',
githubusername: '',
bio: '',
twitter: '',
facebook: '',
linkedin: '',
youtube: '',
instagram: ''
company: "",
website: "",
location: "",
status: "",
skills: "",
githubusername: "",
bio: "",
twitter: "",
facebook: "",
linkedin: "",
youtube: "",
instagram: "",
};
const ProfileForm = ({
profile: { profile, loading },
createProfile,
getCurrentProfile
}) => {
const ProfileForm = () => {
const dispatch = useDispatch();
const { profile, loading } = useSelector((state) => state.profile);
const [formData, setFormData] = useState(initialState);
const creatingProfile = useMatch('/create-profile');
const creatingProfile = useMatch("/create-profile");
const [displaySocialInputs, toggleSocialInputs] = useState(false);
@@ -34,7 +34,10 @@ const ProfileForm = ({
useEffect(() => {
// 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
// then build our profileData
@@ -48,11 +51,11 @@ const ProfileForm = ({
}
// the skills may be an array from our API response
if (Array.isArray(profileData.skills))
profileData.skills = profileData.skills.join(', ');
profileData.skills = profileData.skills.join(", ");
// set local state with the profileData
setFormData(profileData);
}
}, [loading, getCurrentProfile, profile]);
}, [loading, dispatch, profile]);
const {
company,
@@ -66,30 +69,31 @@ const ProfileForm = ({
facebook,
linkedin,
youtube,
instagram
instagram,
} = formData;
const onChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = (e) => {
const onSubmit = async (e) => {
const editing = profile ? true : false;
e.preventDefault();
createProfile(formData, editing).then(() => {
if (!editing) navigate('/dashboard');
await dispatch(createProfile(formData, editing)).then(() => {
console.log(editing)
if (!editing) navigate("/dashboard");
});
};
return (
<section className="container">
<h1 className="large text-primary">
{creatingProfile ? 'Create Your Profile' : 'Edit Your Profile'}
{creatingProfile ? "Create Your Profile" : "Edit Your Profile"}
</h1>
<p className="lead">
<i className="fas fa-user" />
{creatingProfile
? ` Let's get some information to make your`
: ' Add some changes to your profile'}
: " Add some changes to your profile"}
</p>
<small>* = required field</small>
<form className="form" onSubmit={onSubmit}>
@@ -259,17 +263,4 @@ const ProfileForm = ({
);
};
ProfileForm.propTypes = {
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
);
export default ProfileForm;
+21 -25
View File
@@ -1,20 +1,26 @@
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Link, useParams } from 'react-router-dom';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import ProfileTop from './ProfileTop';
import ProfileAbout from './ProfileAbout';
import ProfileExperience from './ProfileExperience';
import ProfileEducation from './ProfileEducation';
import ProfileGithub from './ProfileGithub';
import { getProfileById } from '../../actions/profile';
import React, { Fragment, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import Spinner from "../layout/Spinner";
import ProfileTop from "./ProfileTop";
import ProfileAbout from "./ProfileAbout";
import ProfileExperience from "./ProfileExperience";
import ProfileEducation from "./ProfileEducation";
import ProfileGithub from "./ProfileGithub";
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();
useEffect(() => {
getProfileById(id);
}, [getProfileById, id]);
async function fetchData(){
await dispatch(getProfileById(id));
}
fetchData();
}, [dispatch,id]);
return (
<section className="container">
@@ -76,15 +82,5 @@ const Profile = ({ getProfileById, profile: { profile }, auth }) => {
</section>
);
};
Profile.propTypes = {
getProfileById: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
profile: state.profile,
auth: state.auth
});
export default connect(mapStateToProps, { getProfileById })(Profile);
export default Profile;
@@ -1,5 +1,4 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
const ProfileAbout = ({
profile: {
@@ -27,8 +26,5 @@ const ProfileAbout = ({
</div>
);
ProfileAbout.propTypes = {
profile: PropTypes.object.isRequired
};
export default ProfileAbout;
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import formatDate from '../../utils/formatDate';
const ProfileEducation = ({
@@ -22,9 +21,6 @@ const ProfileEducation = ({
</div>
);
ProfileEducation.propTypes = {
education: PropTypes.object.isRequired
};
export default ProfileEducation;
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import formatDate from '../../utils/formatDate';
const ProfileExperience = ({
@@ -22,9 +21,5 @@ const ProfileExperience = ({
</div>
);
ProfileExperience.propTypes = {
experience: PropTypes.object.isRequired
};
export default ProfileExperience;
+13 -20
View File
@@ -1,17 +1,21 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getGithubRepos } from '../../actions/profile';
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getGithubRepos } from "../../actions/profile";
const ProfileGithub = ({ username, getGithubRepos, repos }) => {
const ProfileGithub = ({ username }) => {
const repos = useSelector((state) => state.profile.repos);
const dispatch = useDispatch();
useEffect(() => {
getGithubRepos(username);
}, [getGithubRepos, username]);
async function fetchData() {
await dispatch(getGithubRepos(username));
}
fetchData();
}, [dispatch, username]);
return (
<div className="profile-github">
<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>
<h4>
@@ -38,15 +42,4 @@ const ProfileGithub = ({ username, getGithubRepos, repos }) => {
);
};
ProfileGithub.propTypes = {
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);
export default ProfileGithub;
+3 -9
View File
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import React from "react";
const ProfileTop = ({
profile: {
@@ -8,8 +7,8 @@ const ProfileTop = ({
location,
website,
social,
user: { name, avatar }
}
user: { name, avatar },
},
}) => {
return (
<div className="profile-top bg-primary p-2">
@@ -44,9 +43,4 @@ const ProfileTop = ({
);
};
ProfileTop.propTypes = {
profile: PropTypes.object.isRequired
};
export default ProfileTop;
@@ -1,6 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
const ProfileItem = ({
profile: {
@@ -35,9 +34,5 @@ const ProfileItem = ({
);
};
ProfileItem.propTypes = {
profile: PropTypes.object.isRequired
};
export default ProfileItem;
+16 -20
View File
@@ -1,15 +1,20 @@
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import ProfileItem from './ProfileItem';
import { getProfiles } from '../../actions/profile';
import React, { Fragment, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import Spinner from "../layout/Spinner";
import ProfileItem from "./ProfileItem";
import { getProfiles } from "../../actions/profile";
const Profiles = ({ getProfiles, profile: { profiles, loading } }) => {
const Profiles = () => {
const dispatch = useDispatch();
useEffect(() => {
getProfiles();
}, [getProfiles]);
async function fetchData() {
await dispatch(getProfiles());
}
fetchData();
}, [dispatch]);
const { profiles, loading } = useSelector((state) => state.profile);
return (
<section className="container">
{loading ? (
@@ -36,13 +41,4 @@ const Profiles = ({ getProfiles, profile: { profiles, loading } }) => {
);
};
Profiles.propTypes = {
getProfiles: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
profile: state.profile
});
export default connect(mapStateToProps, { getProfiles })(Profiles);
export default Profiles;
+7 -19
View File
@@ -1,26 +1,14 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import React from "react";
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import Spinner from "../layout/Spinner";
const PrivateRoute = ({
component: Component,
auth: { isAuthenticated, loading }
}) => {
const PrivateRoute = ({ component: Component }) => {
const { isAuthenticated, loading } = useSelector((state) => state.auth);
if (loading) return <Spinner />;
if (isAuthenticated) return <Component />;
return <Navigate to="/login" />;
};
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
auth: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
export default PrivateRoute;
+5 -2
View File
@@ -1,6 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client'
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/>)
+15 -14
View File
@@ -1,19 +1,20 @@
import { SET_ALERT, REMOVE_ALERT } from '../actions/types';
import { createSlice } from "@reduxjs/toolkit";
const initialState = [];
function alertReducer(state = initialState, action) {
const { type, payload } = action;
const alertSlice = createSlice({
name: "alert",
initialState,
reducers: {
setAlert(state, action) {
return [...state, action.payload];
},
removeAlert(state, action) {
return state.filter((alert) => alert.id !== action.payload);
},
},
});
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}
export const { setAlert, removeAlert } = alertSlice.actions;
export default alertReducer;
export default alertSlice.reducer;
+54 -33
View File
@@ -1,55 +1,76 @@
import {
REGISTER_SUCCESS,
//REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
//LOGIN_FAIL,
LOGOUT,
ACCOUNT_DELETED
} from '../actions/types';
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
token: localStorage.getItem('token'),
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null
user: null,
};
function authReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case USER_LOADED:
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
userLoaded(state, action) {
return {
...state,
isAuthenticated: true,
loading: false,
user: payload
user: action.payload,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
},
registerSuccess(state, action) {
return {
...state,
...payload,
...action.payload,
isAuthenticated: true,
loading: false
loading: false,
};
case ACCOUNT_DELETED:
case AUTH_ERROR:
case LOGOUT:
},
loginSucces(state, action) {
return {
...state,
...action.payload,
isAuthenticated: true,
loading: false,
};
},
accountDeleted(state, action) {
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null
user: null,
};
default:
return state;
}
}
export default authReducer;
},
authError(state, action) {
return {
...state,
token: null,
isAuthenticated: false,
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;
-13
View File
@@ -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
View File
@@ -1,84 +1,92 @@
import {
GET_POSTS,
POST_ERROR,
UPDATE_LIKES,
DELETE_POST,
ADD_POST,
GET_POST,
ADD_COMMENT,
REMOVE_COMMENT
} from '../actions/types';
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
posts: [],
post: null,
loading: true,
error: {}
error: {},
};
function postReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
const postSlice = createSlice({
name: "post",
initialState,
reducers: {
getPostsAction(state, action) {
return {
...state,
posts: payload,
loading: false
posts: action.payload,
loading: false,
};
case GET_POST:
},
getPostAction(state, action) {
return {
...state,
post: payload,
loading: false
post: action.payload,
loading: false,
};
case ADD_POST:
},
addPostAction(state, action) {
return {
...state,
posts: [payload, ...state.posts],
loading: false
posts: [action.payload, ...state.posts],
loading: false,
};
case DELETE_POST:
},
deletePostAction(state, action) {
return {
...state,
posts: state.posts.filter((post) => post._id !== payload),
loading: false
posts: state.posts.filter((post) => post._id !== action.payload),
loading: false,
};
case POST_ERROR:
},
postError(state, action) {
return {
...state,
error: payload,
loading: false
error: action.payload,
loading: false,
};
case UPDATE_LIKES:
},
updateLikes(state, action) {
return {
...state,
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 {
...state,
post: { ...state.post, comments: payload },
loading: false
post: { ...state.post, comments: action.payload },
loading: false,
};
case REMOVE_COMMENT:
},
removeComment(state, action) {
return {
...state,
post: {
...state.post,
comments: state.post.comments.filter(
(comment) => comment._id !== payload
)
(comment) => comment._id !== action.payload
),
},
loading: false
loading: false,
};
default:
return state;
}
}
export default postReducer;
},
},
});
export const {
removeComment,
addCommentAction,
updateLikes,
postError,
deletePostAction,
getPostAction,
getPostsAction,
addPostAction,
} = postSlice.actions;
export default postSlice.reducer;
+41 -33
View File
@@ -1,12 +1,4 @@
import {
GET_PROFILE,
PROFILE_ERROR,
CLEAR_PROFILE,
UPDATE_PROFILE,
GET_PROFILES,
GET_REPOS,
NO_REPOS
} from '../actions/types';
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
profile: null,
@@ -14,54 +6,70 @@ const initialState = {
repos: [],
loading: true,
error: {},
};
function profileReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROFILE:
case UPDATE_PROFILE:
const profileSlice = createSlice({
name: "profile",
initialState,
reducers: {
getProfile(state, action) {
return {
...state,
profile: payload,
profile: action.payload,
loading: false,
};
case GET_PROFILES:
},
updateProfile(state, action) {
return {
...state,
profiles: payload,
profile: action.payload,
loading: false,
};
case PROFILE_ERROR:
},
getProfilesType(state, action) {
return {
...state,
error: payload,
profiles: action.payload,
loading: false,
};
},
profileError(state, action) {
return {
...state,
error: action.payload,
loading: false,
profile: null
profile: null,
};
case CLEAR_PROFILE:
},
clearProfile(state, action) {
return {
...state,
profile: null,
repos: []
repos: [],
};
case GET_REPOS:
},
getRepos(state, action) {
return {
...state,
repos: payload,
repos: action.payload,
loading: false,
};
case NO_REPOS:
},
noRepos(state, action) {
return {
...state,
repos: [],
};
default:
return state;
}
}
},
},
});
export const {
noRepos,
getRepos,
clearProfile,
profileError,
getProfilesType,
updateProfile,
getProfile,
} = profileSlice.actions;
export default profileReducer;
export default profileSlice.reducer;
+12 -20
View File
@@ -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 { 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(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
const store = configureStore({
reducer: {
alert: alertReducer,
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();
store.subscribe(() => {
+1 -2
View File
@@ -1,6 +1,5 @@
import axios from 'axios';
import store from '../store';
import { LOGOUT } from '../actions/types';
const api = axios.create({
baseURL: '/api',
@@ -12,7 +11,7 @@ api.interceptors.response.use(
(res) => res,
(err) => {
if (err.response.status === 401) {
store.dispatch({ type: LOGOUT });
store.dispatch({ type: 'auth/logOut' });
}
return Promise.reject(err);
}
+29276 -1158
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -7,25 +7,25 @@
"start": "node server",
"server": "nodemon server",
"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": "",
"license": "ISC",
"dependencies": {
"axios": "^1.1.3",
"axios": "^0.21.0",
"bcryptjs": "^2.4.3",
"config": "^3.3.8",
"express": "^4.18.2",
"express-validator": "^6.14.2",
"gravatar": "^1.8.2",
"client": "file:client",
"config": "^3.3.3",
"express": "^4.17.1",
"express-validator": "^6.8.1",
"gravatar": "^1.8.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.6.5",
"normalize-url": "^5.0.0",
"request": "^2.88.2",
"uuid": "^9.0.0"
"mongoose": "^5.11.8",
"normalize-url": "^5.3.0"
},
"devDependencies": {
"concurrently": "^7.4.0",
"nodemon": "^2.0.20"
"concurrently": "^5.3.0",
"nodemon": "^2.0.6"
}
}
+2
View File
@@ -15,6 +15,7 @@ app.use('/api/profile', require('./routers/api/profile'))
app.use('/api/posts', require('./routers/api/posts'))
// Serve static assets in production
if (process.env.NODE_ENV==='production'){
app.use(express.static('client/build'));
app.get('*',(req, res)=>[
@@ -22,6 +23,7 @@ if (process.env.NODE_ENV==='production'){
])
}
const PORT = process.env.PORT || 5000;
app.listen(PORT,()=> console.log(`Server started on port ${PORT}`));