now everything works with React ToolKit

This commit is contained in:
QkoSad
2023-06-06 22:28:48 +03:00
parent 35b69eae3f
commit 703784307a
52 changed files with 36817 additions and 7677 deletions
+1
View File
@@ -1,3 +1,4 @@
/* Global Styles */
:root {
--primary-color: #17a2b8;
+13 -4
View File
@@ -16,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';
@@ -25,6 +24,7 @@ import { loadUser } from './actions/auth';
import setAuthToken from './utils/setAuthToken';
import './App.css';
import { logOut } from './reducers/auth';
// Level - 1
//
@@ -42,13 +42,12 @@ 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);
});
}, []);
return (
<Provider store={store}>
// Seting Up redux store
<Provider store={store}>
<Router>
<Navbar />
<Alert />
@@ -85,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 -84
View File
@@ -1,32 +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';
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 })
);
}
};
@@ -35,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 })
);
}
};
@@ -52,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 })
);
}
};
@@ -69,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 })
);
}
};
@@ -107,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 })
);
}
};
@@ -124,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 })
);
}
};
@@ -143,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';
View File
+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;
+21 -34
View File
@@ -1,23 +1,21 @@
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';
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";
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>
@@ -31,7 +29,10 @@ const Dashboard = ({
<Education education={profile.education} />
<div className="my-2">
<button className="btn btn-danger" onClick={() => deleteAccount()}>
<button
className="btn btn-danger"
onClick={async () => await dispatch(deleteAccount())}
>
<i className="fas fa-user" /> Delete My Account
</button>
</div>
@@ -48,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;
+10 -19
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">Profile</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>
@@ -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;
+6 -25
View File
@@ -1,14 +1,10 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { useDispatch } from "react-redux";
import { addPost } from "../../actions/post";
const PostForm = ({ addPost }) => {
const PostForm = () => {
const [text, setText] = useState("");
const [category, setCategory] = useState("");
//const onChange = (e) =>
//setFormData({ ...formData, [e.target.name]: e.target.value });
const dispatch = useDispatch();
return (
<div className="post-form">
<div className="bg-primary p">
@@ -16,9 +12,9 @@ const PostForm = ({ addPost }) => {
</div>
<form
className="form my-1"
onSubmit={(e) => {
onSubmit={async (e) => {
e.preventDefault();
addPost({ text, category });
await dispatch(addPost({ text }));
setText("");
}}
>
@@ -31,25 +27,10 @@ const PostForm = ({ addPost }) => {
onChange={(e) => setText(e.target.value)}
required
/>
<p className="lead">Choose a category:</p>
<select name="category" value={category} onChange={setCategory}>
<option>* Select Category</option>
<option value="opinion">Opinion</option>
<option value="question">Question</option>
<option value="asssitance">Asking for asssitance</option>
<option value="news">News</option>
<option value="other">Other</option>
</select>
<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;
+4 -3
View File
@@ -1,8 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client'
import App from './App';
// Level - 0
//Program stars from Here. Imports and renders App.
ReactDOM.render(<App />, document.getElementById('root'));
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;
+53 -43
View File
@@ -1,65 +1,75 @@
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,
profiles: [],
repos: [],
loading: true,
error: {}
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,
loading: false
};
case GET_PROFILES:
return {
...state,
profiles: payload,
loading: false
};
case PROFILE_ERROR:
return {
...state,
error: payload,
profile: action.payload,
loading: false,
profile: null
};
case CLEAR_PROFILE:
},
updateProfile(state, action) {
return {
...state,
profile: action.payload,
loading: false,
};
},
getProfilesType(state, action) {
return {
...state,
profiles: action.payload,
loading: false,
};
},
profileError(state, action) {
return {
...state,
error: action.payload,
loading: false,
profile: null,
};
},
clearProfile(state, action) {
return {
...state,
profile: null,
repos: []
repos: [],
};
case GET_REPOS:
},
getRepos(state, action) {
return {
...state,
repos: payload,
loading: false
repos: action.payload,
loading: false,
};
case NO_REPOS:
},
noRepos(state, action) {
return {
...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
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);
}