project done
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
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';
|
||||
|
||||
const Login = ({ login, isAuthenticated }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const { email, password } = formData;
|
||||
|
||||
const onChange = (e) =>
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
login(email, password);
|
||||
};
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to="/dashboard" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Sign In</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-user" /> Sign Into Your Account
|
||||
</p>
|
||||
<form className="form" onSubmit={onSubmit}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email Address"
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={onChange}
|
||||
minLength="6"
|
||||
/>
|
||||
</div>
|
||||
<input type="submit" className="btn btn-primary" value="Login" />
|
||||
</form>
|
||||
<p className="my-1">
|
||||
Don't have an account? <Link to="/register">Sign Up</Link>
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Login.propTypes = {
|
||||
login: PropTypes.func.isRequired,
|
||||
isAuthenticated: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isAuthenticated: state.auth.isAuthenticated
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { login })(Login);
|
||||
@@ -0,0 +1,100 @@
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, Navigate } from 'react-router-dom';
|
||||
import { setAlert } from '../../actions/alert';
|
||||
import { register } from '../../actions/auth';
|
||||
|
||||
const Register = ({ setAlert, register, isAuthenticated }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
});
|
||||
|
||||
const { name, email, password, password2 } = formData;
|
||||
|
||||
const onChange = (e) =>
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (password !== password2) {
|
||||
setAlert('Passwords do not match', 'danger');
|
||||
} else {
|
||||
register({ name, email, password });
|
||||
}
|
||||
};
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to="/dashboard" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Sign Up</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-user" /> Create Your Account
|
||||
</p>
|
||||
<form className="form" onSubmit={onSubmit}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
name="name"
|
||||
value={name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email Address"
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
This site uses Gravatar so if you want a profile image, use a
|
||||
Gravatar email
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
name="password2"
|
||||
value={password2}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<input type="submit" className="btn btn-primary" value="Register" />
|
||||
</form>
|
||||
<p className="my-1">
|
||||
Already have an account? <Link to="/login">Sign In</Link>
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,64 @@
|
||||
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 },
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
getCurrentProfile();
|
||||
}, [getCurrentProfile]);
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Dashboard</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-user" /> Welcome {user && user.name}
|
||||
</p>
|
||||
{profile !== null ? (
|
||||
<>
|
||||
<DashboardActions />
|
||||
<Experience experience={profile.experience} />
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>You have not yet setup a profile, please add some info</p>
|
||||
<Link to="/create-profile" className="btn btn-primary my-1">
|
||||
Create Profile
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const DashboardActions = () => {
|
||||
return (
|
||||
<div className='dash-buttons'>
|
||||
<Link to='/edit-profile' className='btn btn-light'>
|
||||
<i className='fas fa-user-circle text-primary' /> Edit Profile
|
||||
</Link>
|
||||
<Link to='/add-experience' className='btn btn-light'>
|
||||
<i className='fab fa-black-tie text-primary' /> Add Experience
|
||||
</Link>
|
||||
<Link to='/add-education' className='btn btn-light'>
|
||||
<i className='fas fa-graduation-cap text-primary' /> Add Education
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardActions;
|
||||
@@ -0,0 +1,49 @@
|
||||
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';
|
||||
|
||||
const Education = ({ education, deleteEducation }) => {
|
||||
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'}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
onClick={() => deleteEducation(edu._id)}
|
||||
className="btn btn-danger"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2 className="my-2">Education Credentials</h2>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>School</th>
|
||||
<th className="hide-sm">Degree</th>
|
||||
<th className="hide-sm">Years</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{educations}</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Education.propTypes = {
|
||||
education: PropTypes.array.isRequired,
|
||||
deleteEducation: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, { deleteEducation })(Education);
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { deleteExperience } from '../../actions/profile';
|
||||
import formatDate from '../../utils/formatDate';
|
||||
|
||||
const Experience = ({ experience, deleteExperience }) => {
|
||||
const experiences = experience.map((exp) => (
|
||||
<tr key={exp._id}>
|
||||
<td>{exp.company}</td>
|
||||
<td className="hide-sm">{exp.title}</td>
|
||||
<td>
|
||||
{formatDate(exp.from)} - {exp.to ? formatDate(exp.to) : 'Now'}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
onClick={() => deleteExperience(exp._id)}
|
||||
className="btn btn-danger"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2 className="my-2">Experience Credentials</h2>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th className="hide-sm">Title</th>
|
||||
<th className="hide-sm">Years</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{experiences}</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Experience.propTypes = {
|
||||
experience: PropTypes.array.isRequired,
|
||||
deleteExperience: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, { deleteExperience })(Experience);
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
alerts: state.alert
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Alert);
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Link, Navigate } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Landing = ({ isAuthenticated }) => {
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to='/dashboard' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className='landing'>
|
||||
<div className='dark-overlay'>
|
||||
<div className='landing-inner'>
|
||||
<h1 className='x-large'>Developer Connector</h1>
|
||||
<p className='lead'>
|
||||
Create a developer profile/portfolio, share posts and get help from
|
||||
other developers
|
||||
</p>
|
||||
<div className='buttons'>
|
||||
<Link to='/register' className='btn btn-primary'>
|
||||
Sign Up
|
||||
</Link>
|
||||
<Link to='/login' className='btn btn-light'>
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Landing.propTypes = {
|
||||
isAuthenticated: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isAuthenticated: state.auth.isAuthenticated
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Landing);
|
||||
@@ -0,0 +1,66 @@
|
||||
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';
|
||||
|
||||
const Navbar = ({ auth: { isAuthenticated }, logout }) => {
|
||||
const authLinks = (
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/profiles">Developers</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/posts">Posts</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/dashboard">
|
||||
<i className="fas fa-user" />{' '}
|
||||
<span className="hide-sm">Dashboard</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={logout} href="#!">
|
||||
<i className="fas fa-sign-out-alt" />{' '}
|
||||
<span className="hide-sm">Logout</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
const guestLinks = (
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/profiles">Developers</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/register">Register</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/login">Login</Link>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
return (
|
||||
<nav className="navbar bg-dark">
|
||||
<h1>
|
||||
<Link to="/">
|
||||
<i className="fas fa-code" /> DevConnector
|
||||
</Link>
|
||||
</h1>
|
||||
<Fragment>{isAuthenticated ? authLinks : guestLinks}</Fragment>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
Navbar.propTypes = {
|
||||
logout: PropTypes.func.isRequired,
|
||||
auth: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { logout })(Navbar);
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="x-large text-primary">
|
||||
<i className="fas fa-exclamation-triangle" /> Page Not Found
|
||||
</h1>
|
||||
<p className="large">Sorry, this page does not exist</p>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import spinner from './spinner.gif';
|
||||
|
||||
const Spinner = () => (
|
||||
<Fragment>
|
||||
<img
|
||||
src={spinner}
|
||||
style={{ width: '200px', margin: 'auto', display: 'block' }}
|
||||
alt="Loading..."
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default Spinner;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -0,0 +1,44 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { addComment } from '../../actions/post';
|
||||
|
||||
const CommentForm = ({ postId, addComment }) => {
|
||||
const [text, setText] = useState('');
|
||||
|
||||
return (
|
||||
<div className='post-form'>
|
||||
<div className='bg-primary p'>
|
||||
<h3>Leave a Comment</h3>
|
||||
</div>
|
||||
<form
|
||||
className='form my-1'
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
addComment(postId, { text });
|
||||
setText('');
|
||||
}}
|
||||
>
|
||||
<textarea
|
||||
name='text'
|
||||
cols='30'
|
||||
rows='5'
|
||||
placeholder='Comment the post'
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input type='submit' className='btn btn-dark my-1' value='Submit' />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CommentForm.propTypes = {
|
||||
addComment: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{ addComment }
|
||||
)(CommentForm);
|
||||
@@ -0,0 +1,48 @@
|
||||
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';
|
||||
|
||||
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>
|
||||
</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);
|
||||
@@ -0,0 +1,44 @@
|
||||
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';
|
||||
|
||||
const Post = ({ getPost, post: { post, loading } }) => {
|
||||
const { id } = useParams();
|
||||
useEffect(() => {
|
||||
getPost(id);
|
||||
}, [getPost, id]);
|
||||
|
||||
return loading || post === null ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<section className="container">
|
||||
<Link to="/posts" className="btn">
|
||||
Back To Posts
|
||||
</Link>
|
||||
<PostItem post={post} showActions={false} />
|
||||
<CommentForm postId={post._id} />
|
||||
<div className="comments">
|
||||
{post.comments.map((comment) => (
|
||||
<CommentItem key={comment._id} comment={comment} postId={post._id} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Post.propTypes = {
|
||||
getPost: PropTypes.func.isRequired,
|
||||
post: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
post: state.post
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getPost })(Post);
|
||||
@@ -0,0 +1,44 @@
|
||||
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('');
|
||||
|
||||
return (
|
||||
<div className='post-form'>
|
||||
<div className='bg-primary p'>
|
||||
<h3>Say Something...</h3>
|
||||
</div>
|
||||
<form
|
||||
className='form my-1'
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
addPost({ text });
|
||||
setText('');
|
||||
}}
|
||||
>
|
||||
<textarea
|
||||
name='text'
|
||||
cols='30'
|
||||
rows='5'
|
||||
placeholder='Create a post'
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<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);
|
||||
@@ -0,0 +1,84 @@
|
||||
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';
|
||||
|
||||
const PostItem = ({
|
||||
addLike,
|
||||
removeLike,
|
||||
deletePost,
|
||||
auth,
|
||||
post: { _id, text, name, avatar, user, likes, comments, date },
|
||||
showActions
|
||||
}) => (
|
||||
<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>
|
||||
|
||||
{showActions && (
|
||||
<Fragment>
|
||||
<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 && (
|
||||
<button
|
||||
onClick={() => deletePost(_id)}
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
>
|
||||
<i className="fas fa-times" />
|
||||
</button>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
PostItem.defaultProps = {
|
||||
showActions: true
|
||||
};
|
||||
|
||||
PostItem.propTypes = {
|
||||
post: PropTypes.object.isRequired,
|
||||
auth: PropTypes.object.isRequired,
|
||||
addLike: PropTypes.func.isRequired,
|
||||
removeLike: PropTypes.func.isRequired,
|
||||
deletePost: PropTypes.func.isRequired,
|
||||
showActions: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { addLike, removeLike, deletePost })(
|
||||
PostItem
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
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';
|
||||
|
||||
const Posts = ({ getPosts, post: { posts } }) => {
|
||||
useEffect(() => {
|
||||
getPosts();
|
||||
}, [getPosts]);
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Posts</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-user" /> Welcome to the community
|
||||
</p>
|
||||
<PostForm />
|
||||
<div className="posts">
|
||||
{posts.map((post) => (
|
||||
<PostItem key={post._id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Posts.propTypes = {
|
||||
getPosts: PropTypes.func.isRequired,
|
||||
post: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
post: state.post
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getPosts })(Posts);
|
||||
@@ -0,0 +1,118 @@
|
||||
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';
|
||||
|
||||
const AddEducation = ({ addEducation }) => {
|
||||
const navigate = useNavigate();
|
||||
const [formData, setFormData] = useState({
|
||||
school: '',
|
||||
degree: '',
|
||||
fieldofstudy: '',
|
||||
from: '',
|
||||
to: '',
|
||||
current: false,
|
||||
description: ''
|
||||
});
|
||||
|
||||
const { school, degree, fieldofstudy, from, to, description, current } =
|
||||
formData;
|
||||
|
||||
const onChange = (e) =>
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Add Your Education</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-code-branch" /> Add any school or bootcamp that you
|
||||
have attended
|
||||
</p>
|
||||
<small>* = required field</small>
|
||||
<form
|
||||
className="form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
addEducation(formData, navigate);
|
||||
}}
|
||||
>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* School or Bootcamp"
|
||||
name="school"
|
||||
value={school}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Degree or Certificate"
|
||||
name="degree"
|
||||
value={degree}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Field of Study"
|
||||
name="fieldofstudy"
|
||||
value={fieldofstudy}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>From Date</h4>
|
||||
<input type="date" name="from" value={from} onChange={onChange} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="current"
|
||||
checked={current}
|
||||
value={current}
|
||||
onChange={() => setFormData({ ...formData, current: !current })}
|
||||
/>{' '}
|
||||
Current School
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>To Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="to"
|
||||
value={to}
|
||||
onChange={onChange}
|
||||
disabled={current}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
name="description"
|
||||
cols="30"
|
||||
rows="5"
|
||||
placeholder="Program Description"
|
||||
value={description}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<input type="submit" className="btn btn-primary my-1" />
|
||||
<Link className="btn btn-light my-1" to="/dashboard">
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
AddEducation.propTypes = {
|
||||
addEducation: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, { addEducation })(AddEducation);
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { addExperience } from '../../actions/profile';
|
||||
|
||||
const AddExperience = ({ addExperience }) => {
|
||||
const navigate = useNavigate();
|
||||
const [formData, setFormData] = useState({
|
||||
company: '',
|
||||
title: '',
|
||||
location: '',
|
||||
from: '',
|
||||
to: '',
|
||||
current: false,
|
||||
description: ''
|
||||
});
|
||||
|
||||
const { company, title, location, from, to, current, description } = formData;
|
||||
|
||||
const onChange = (e) =>
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">Add An Experience</h1>
|
||||
<p className="lead">
|
||||
<i className="fas fa-code-branch" /> Add any developer/programming
|
||||
positions that you have had in the past
|
||||
</p>
|
||||
<small>* = required field</small>
|
||||
<form
|
||||
className="form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
addExperience(formData, navigate);
|
||||
}}
|
||||
>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Job Title"
|
||||
name="title"
|
||||
value={title}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Company"
|
||||
name="company"
|
||||
value={company}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location"
|
||||
name="location"
|
||||
value={location}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>From Date</h4>
|
||||
<input type="date" name="from" value={from} onChange={onChange} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<p>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="current"
|
||||
checked={current}
|
||||
value={current}
|
||||
onChange={() => {
|
||||
setFormData({ ...formData, current: !current });
|
||||
}}
|
||||
/>{' '}
|
||||
Current Job
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<h4>To Date</h4>
|
||||
<input
|
||||
type="date"
|
||||
name="to"
|
||||
value={to}
|
||||
onChange={onChange}
|
||||
disabled={current}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
name="description"
|
||||
cols="30"
|
||||
rows="5"
|
||||
placeholder="Job Description"
|
||||
value={description}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<input type="submit" className="btn btn-primary my-1" />
|
||||
<Link className="btn btn-light my-1" to="/dashboard">
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
AddExperience.propTypes = {
|
||||
addExperience: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, { addExperience })(AddExperience);
|
||||
@@ -0,0 +1,276 @@
|
||||
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';
|
||||
|
||||
/*
|
||||
NOTE: declare initialState outside of component
|
||||
so that it doesn't trigger a useEffect
|
||||
we can then safely use this to construct our profileData
|
||||
*/
|
||||
const initialState = {
|
||||
company: '',
|
||||
website: '',
|
||||
location: '',
|
||||
status: '',
|
||||
skills: '',
|
||||
githubusername: '',
|
||||
bio: '',
|
||||
twitter: '',
|
||||
facebook: '',
|
||||
linkedin: '',
|
||||
youtube: '',
|
||||
instagram: ''
|
||||
};
|
||||
|
||||
const ProfileForm = ({
|
||||
profile: { profile, loading },
|
||||
createProfile,
|
||||
getCurrentProfile
|
||||
}) => {
|
||||
const [formData, setFormData] = useState(initialState);
|
||||
|
||||
const creatingProfile = useMatch('/create-profile');
|
||||
|
||||
const [displaySocialInputs, toggleSocialInputs] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// if there is no profile, attempt to fetch one
|
||||
if (!profile) getCurrentProfile();
|
||||
|
||||
// if we finished loading and we do have a profile
|
||||
// then build our profileData
|
||||
if (!loading && profile) {
|
||||
const profileData = { ...initialState };
|
||||
for (const key in profile) {
|
||||
if (key in profileData) profileData[key] = profile[key];
|
||||
}
|
||||
for (const key in profile.social) {
|
||||
if (key in profileData) profileData[key] = profile.social[key];
|
||||
}
|
||||
// the skills may be an array from our API response
|
||||
if (Array.isArray(profileData.skills))
|
||||
profileData.skills = profileData.skills.join(', ');
|
||||
// set local state with the profileData
|
||||
setFormData(profileData);
|
||||
}
|
||||
}, [loading, getCurrentProfile, profile]);
|
||||
|
||||
const {
|
||||
company,
|
||||
website,
|
||||
location,
|
||||
status,
|
||||
skills,
|
||||
githubusername,
|
||||
bio,
|
||||
twitter,
|
||||
facebook,
|
||||
linkedin,
|
||||
youtube,
|
||||
instagram
|
||||
} = formData;
|
||||
|
||||
const onChange = (e) =>
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
createProfile(formData, navigate, profile ? true : false);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
<h1 className="large text-primary">
|
||||
{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'}
|
||||
</p>
|
||||
<small>* = required field</small>
|
||||
<form className="form" onSubmit={onSubmit}>
|
||||
<div className="form-group">
|
||||
<select name="status" value={status} onChange={onChange}>
|
||||
<option>* Select Professional Status</option>
|
||||
<option value="Developer">Developer</option>
|
||||
<option value="Junior Developer">Junior Developer</option>
|
||||
<option value="Senior Developer">Senior Developer</option>
|
||||
<option value="Manager">Manager</option>
|
||||
<option value="Student or Learning">Student or Learning</option>
|
||||
<option value="Instructor">Instructor or Teacher</option>
|
||||
<option value="Intern">Intern</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
<small className="form-text">
|
||||
Give us an idea of where you are at in your career
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Company"
|
||||
name="company"
|
||||
value={company}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Could be your own company or one you work for
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Website"
|
||||
name="website"
|
||||
value={website}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Could be your own or a company website
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Location"
|
||||
name="location"
|
||||
value={location}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
City & state suggested (eg. Boston, MA)
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="* Skills"
|
||||
name="skills"
|
||||
value={skills}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
Please use comma separated values (eg. HTML,CSS,JavaScript,PHP)
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Github Username"
|
||||
name="githubusername"
|
||||
value={githubusername}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">
|
||||
If you want your latest repos and a Github link, include your
|
||||
username
|
||||
</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
placeholder="A short bio of yourself"
|
||||
name="bio"
|
||||
value={bio}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<small className="form-text">Tell us a little about yourself</small>
|
||||
</div>
|
||||
|
||||
<div className="my-2">
|
||||
<button
|
||||
onClick={() => toggleSocialInputs(!displaySocialInputs)}
|
||||
type="button"
|
||||
className="btn btn-light"
|
||||
>
|
||||
Add Social Network Links
|
||||
</button>
|
||||
<span>Optional</span>
|
||||
</div>
|
||||
|
||||
{displaySocialInputs && (
|
||||
<Fragment>
|
||||
<div className="form-group social-input">
|
||||
<i className="fab fa-twitter fa-2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Twitter URL"
|
||||
name="twitter"
|
||||
value={twitter}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<i className="fab fa-facebook fa-2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Facebook URL"
|
||||
name="facebook"
|
||||
value={facebook}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<i className="fab fa-youtube fa-2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="YouTube URL"
|
||||
name="youtube"
|
||||
value={youtube}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<i className="fab fa-linkedin fa-2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Linkedin URL"
|
||||
name="linkedin"
|
||||
value={linkedin}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group social-input">
|
||||
<i className="fab fa-instagram fa-2x" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Instagram URL"
|
||||
name="instagram"
|
||||
value={instagram}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<input type="submit" className="btn btn-primary my-1" />
|
||||
<Link className="btn btn-light my-1" to="/dashboard">
|
||||
Go Back
|
||||
</Link>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
@@ -0,0 +1,89 @@
|
||||
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';
|
||||
|
||||
const Profile = ({ getProfileById, profile: { profile }, auth }) => {
|
||||
const { id } = useParams();
|
||||
useEffect(() => {
|
||||
getProfileById(id);
|
||||
}, [getProfileById, id]);
|
||||
|
||||
return (
|
||||
<section className="container">
|
||||
{profile === null ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<Fragment>
|
||||
<Link to="/profiles" className="btn btn-light">
|
||||
Back To Profiles
|
||||
</Link>
|
||||
{auth.isAuthenticated &&
|
||||
auth.loading === false &&
|
||||
auth.user._id === profile.user._id && (
|
||||
<Link to="/edit-profile" className="btn btn-dark">
|
||||
Edit Profile
|
||||
</Link>
|
||||
)}
|
||||
<div className="profile-grid my-1">
|
||||
<ProfileTop profile={profile} />
|
||||
<ProfileAbout profile={profile} />
|
||||
<div className="profile-exp bg-white p-2">
|
||||
<h2 className="text-primary">Experience</h2>
|
||||
{profile.experience.length > 0 ? (
|
||||
<Fragment>
|
||||
{profile.experience.map((experience) => (
|
||||
<ProfileExperience
|
||||
key={experience._id}
|
||||
experience={experience}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
) : (
|
||||
<h4>No experience credentials</h4>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="profile-edu bg-white p-2">
|
||||
<h2 className="text-primary">Education</h2>
|
||||
{profile.education.length > 0 ? (
|
||||
<Fragment>
|
||||
{profile.education.map((education) => (
|
||||
<ProfileEducation
|
||||
key={education._id}
|
||||
education={education}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
) : (
|
||||
<h4>No education credentials</h4>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{profile.githubusername && (
|
||||
<ProfileGithub username={profile.githubusername} />
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</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);
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ProfileAbout = ({
|
||||
profile: {
|
||||
bio,
|
||||
skills,
|
||||
user: { name }
|
||||
}
|
||||
}) => (
|
||||
<div className='profile-about bg-light p-2'>
|
||||
{bio && (
|
||||
<Fragment>
|
||||
<h2 className='text-primary'>{name.trim().split(' ')[0]}s Bio</h2>
|
||||
<p>{bio}</p>
|
||||
<div className='line' />
|
||||
</Fragment>
|
||||
)}
|
||||
<h2 className='text-primary'>Skill Set</h2>
|
||||
<div className='skills'>
|
||||
{skills.map((skill, index) => (
|
||||
<div key={index} className='p-1'>
|
||||
<i className='fas fa-check' /> {skill}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ProfileAbout.propTypes = {
|
||||
profile: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileAbout;
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import formatDate from '../../utils/formatDate';
|
||||
|
||||
const ProfileEducation = ({
|
||||
education: { school, degree, fieldofstudy, current, to, from, description }
|
||||
}) => (
|
||||
<div>
|
||||
<h3 className="text-dark">{school}</h3>
|
||||
<p>
|
||||
{formatDate(from)} - {to ? formatDate(to) : 'Now'}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Degree: </strong> {degree}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Field Of Study: </strong> {fieldofstudy}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Description: </strong> {description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
ProfileEducation.propTypes = {
|
||||
education: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileEducation;
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import formatDate from '../../utils/formatDate';
|
||||
|
||||
const ProfileExperience = ({
|
||||
experience: { company, title, location, current, to, from, description }
|
||||
}) => (
|
||||
<div>
|
||||
<h3 className="text-dark">{company}</h3>
|
||||
<p>
|
||||
{formatDate(from)} - {to ? formatDate(to) : 'Now'}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Position: </strong> {title}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Location: </strong> {location}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Description: </strong> {description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
ProfileExperience.propTypes = {
|
||||
experience: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileExperience;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { getGithubRepos } from '../../actions/profile';
|
||||
|
||||
const ProfileGithub = ({ username, getGithubRepos, repos }) => {
|
||||
useEffect(() => {
|
||||
getGithubRepos(username);
|
||||
}, [getGithubRepos, username]);
|
||||
|
||||
return (
|
||||
<div className="profile-github">
|
||||
<h2 className="text-primary my-1">Github Repos</h2>
|
||||
{repos.map(repo => (
|
||||
<div key={repo.id} className="repo bg-white p-1 my-1">
|
||||
<div>
|
||||
<h4>
|
||||
<a href={repo.html_url} target="_blank" rel="noopener noreferrer">
|
||||
{repo.name}
|
||||
</a>
|
||||
</h4>
|
||||
<p>{repo.description}</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li className="badge badge-primary">
|
||||
Stars: {repo.stargazers_count}
|
||||
</li>
|
||||
<li className="badge badge-dark">
|
||||
Watchers: {repo.watchers_count}
|
||||
</li>
|
||||
<li className="badge badge-light">Forks: {repo.forks_count}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ProfileTop = ({
|
||||
profile: {
|
||||
status,
|
||||
company,
|
||||
location,
|
||||
website,
|
||||
social,
|
||||
user: { name, avatar }
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
<div className="profile-top bg-primary p-2">
|
||||
<img className="round-img my-1" src={avatar} alt="" />
|
||||
<h1 className="large">{name}</h1>
|
||||
<p className="lead">
|
||||
{status} {company ? <span> at {company}</span> : null}
|
||||
</p>
|
||||
<p>{location ? <span>{location}</span> : null}</p>
|
||||
<div className="icons my-1">
|
||||
{website ? (
|
||||
<a href={website} target="_blank" rel="noopener noreferrer">
|
||||
<i className="fas fa-globe fa-2x" />
|
||||
</a>
|
||||
) : null}
|
||||
{social
|
||||
? Object.entries(social)
|
||||
.filter(([_, value]) => value)
|
||||
.map(([key, value]) => (
|
||||
<a
|
||||
key={key}
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<i className={`fab fa-${key} fa-2x`}></i>
|
||||
</a>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ProfileTop.propTypes = {
|
||||
profile: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileTop;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ProfileItem = ({
|
||||
profile: {
|
||||
user: { _id, name, avatar },
|
||||
status,
|
||||
company,
|
||||
location,
|
||||
skills
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
<div className='profile bg-light'>
|
||||
<img src={avatar} alt='' className='round-img' />
|
||||
<div>
|
||||
<h2>{name}</h2>
|
||||
<p>
|
||||
{status} {company && <span> at {company}</span>}
|
||||
</p>
|
||||
<p className='my-1'>{location && <span>{location}</span>}</p>
|
||||
<Link to={`/profile/${_id}`} className='btn btn-primary'>
|
||||
View Profile
|
||||
</Link>
|
||||
</div>
|
||||
<ul>
|
||||
{skills.slice(0, 4).map((skill, index) => (
|
||||
<li key={index} className='text-primary'>
|
||||
<i className='fas fa-check' /> {skill}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ProfileItem.propTypes = {
|
||||
profile: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileItem;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ProfileItem = ({
|
||||
profile: {
|
||||
user: { _id, name, avatar },
|
||||
status,
|
||||
company,
|
||||
location,
|
||||
skills
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
<div className='profile bg-light'>
|
||||
<img src={avatar} alt='' className='round-img' />
|
||||
<div>
|
||||
<h2>{name}</h2>
|
||||
<p>
|
||||
{status} {company && <span> at {company}</span>}
|
||||
</p>
|
||||
<p className='my-1'>{location && <span>{location}</span>}</p>
|
||||
<Link to={`/profile/${_id}`} className='btn btn-primary'>
|
||||
View Profile
|
||||
</Link>
|
||||
</div>
|
||||
<ul>
|
||||
{skills.slice(0, 4).map((skill, index) => (
|
||||
<li key={index} className='text-primary'>
|
||||
<i className='fas fa-check' /> {skill}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ProfileItem.propTypes = {
|
||||
profile: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ProfileItem;
|
||||
@@ -0,0 +1,25 @@
|
||||
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';
|
||||
|
||||
const PrivateRoute = ({
|
||||
component: Component,
|
||||
auth: { isAuthenticated, loading }
|
||||
}) => {
|
||||
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);
|
||||
Reference in New Issue
Block a user