project done

This commit is contained in:
QkoSad
2022-11-07 17:15:55 +02:00
commit 543d271672
68 changed files with 37051 additions and 0 deletions
+71
View File
@@ -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);
+100
View File
@@ -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);
+23
View File
@@ -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);
+42
View File
@@ -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);
+66
View File
@@ -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);
+14
View File
@@ -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;
+14
View File
@@ -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

+44
View File
@@ -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);
+48
View File
@@ -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);
+44
View File
@@ -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);
+44
View File
@@ -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);
+84
View File
@@ -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
);
+38
View File
@@ -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
);
+89
View File
@@ -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);