├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.js
├── MainRouter.js
├── admin
│ ├── Admin.js
│ └── test.js
├── auth
│ ├── PrivateRoute.js
│ └── index.js
├── core
│ ├── Home.js
│ └── Menu.js
├── images
│ ├── avatar.jpg
│ └── mountains.jpg
├── index.js
├── post
│ ├── Comment.js
│ ├── EditPost.js
│ ├── NewPost.js
│ ├── Posts.js
│ ├── SinglePost.js
│ └── apiPost.js
└── user
│ ├── DeleteUser.js
│ ├── EditProfile.js
│ ├── FindPeople.js
│ ├── FollowProfileButton.js
│ ├── ForgotPassword.js
│ ├── Profile.js
│ ├── ProfileTabs.js
│ ├── ResetPassword.js
│ ├── Signin.js
│ ├── Signup.js
│ ├── SocialLogin.js
│ ├── Users.js
│ └── apiUser.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .env
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Source code for the Udemy Course
2 |
3 | ### [React Node Social Network from Scratch to Deployment ](https://www.udemy.com/node-react/?couponCode=GITHUB)
4 |
5 | ### To run this project, do the following:
6 |
7 | ##### create .env with the following code (update credentials). Make sure to create .env in the root of the project, not inside /src. react-front/.env
8 |
9 | ```
10 | REACT_APP_API_URL=http://localhost:8080/api
11 | REACT_APP_GOOGLE_CLIENT_ID=xxxxxx.apps.googleusercontent.com
12 | ```
13 |
14 | ##### Then run the following commands to start up the app
15 |
16 | ```
17 | cd react-front
18 | npm install
19 | npm start
20 | ```
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "lodash": "^4.17.11",
7 | "react": "^16.7.0",
8 | "react-dom": "^16.7.0",
9 | "react-google-login": "^5.0.2",
10 | "react-google-recaptcha": "^1.0.5",
11 | "react-recaptcha": "^2.3.10",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^3.2.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": [
25 | ">0.2%",
26 | "not dead",
27 | "not ie <= 11",
28 | "not op_mini all"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/react-front/be63af731d2c353460f6c3e12d54654855e617f2/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
10 | isAuthenticated() ? (
11 |
12 | ) : (
13 |
19 | )
20 | }
21 | />
22 | );
23 |
24 | export default PrivateRoute;
25 |
--------------------------------------------------------------------------------
/src/auth/index.js:
--------------------------------------------------------------------------------
1 | export const signup = user => {
2 | return fetch(`${process.env.REACT_APP_API_URL}/signup`, {
3 | method: 'POST',
4 | headers: {
5 | Accept: 'application/json',
6 | 'Content-Type': 'application/json'
7 | },
8 | body: JSON.stringify(user)
9 | })
10 | .then(response => {
11 | return response.json();
12 | })
13 | .catch(err => console.log(err));
14 | };
15 |
16 | export const signin = user => {
17 | return fetch(`${process.env.REACT_APP_API_URL}/signin`, {
18 | method: 'POST',
19 | headers: {
20 | Accept: 'application/json',
21 | 'Content-Type': 'application/json'
22 | },
23 | body: JSON.stringify(user)
24 | })
25 | .then(response => {
26 | return response.json();
27 | })
28 | .catch(err => console.log(err));
29 | };
30 |
31 | export const authenticate = (jwt, next) => {
32 | if (typeof window !== 'undefined') {
33 | localStorage.setItem('jwt', JSON.stringify(jwt));
34 | next();
35 | }
36 | };
37 |
38 | export const setName = (name, next) => {
39 | if (typeof window !== 'undefined') {
40 | localStorage.setItem('username', JSON.stringify(name));
41 | next();
42 | }
43 | };
44 |
45 | export const signout = next => {
46 | if (typeof window !== 'undefined') localStorage.removeItem('jwt');
47 | next();
48 | return fetch(`${process.env.REACT_APP_API_URL}/signout`, {
49 | method: 'GET'
50 | })
51 | .then(response => {
52 | console.log('signout', response);
53 | return response.json();
54 | })
55 | .catch(err => console.log(err));
56 | };
57 |
58 | export const isAuthenticated = () => {
59 | if (typeof window == 'undefined') {
60 | return false;
61 | }
62 |
63 | if (localStorage.getItem('jwt')) {
64 | return JSON.parse(localStorage.getItem('jwt'));
65 | } else {
66 | return false;
67 | }
68 | };
69 |
70 | export const forgotPassword = email => {
71 | console.log('email: ', email);
72 | return fetch(`${process.env.REACT_APP_API_URL}/forgot-password/`, {
73 | method: 'PUT',
74 | headers: {
75 | Accept: 'application/json',
76 | 'Content-Type': 'application/json'
77 | },
78 | body: JSON.stringify({ email })
79 | })
80 | .then(response => {
81 | console.log('forgot password response: ', response);
82 | return response.json();
83 | })
84 | .catch(err => console.log(err));
85 | };
86 |
87 | export const resetPassword = resetInfo => {
88 | return fetch(`${process.env.REACT_APP_API_URL}/reset-password/`, {
89 | method: 'PUT',
90 | headers: {
91 | Accept: 'application/json',
92 | 'Content-Type': 'application/json'
93 | },
94 | body: JSON.stringify(resetInfo)
95 | })
96 | .then(response => {
97 | console.log('forgot password response: ', response);
98 | return response.json();
99 | })
100 | .catch(err => console.log(err));
101 | };
102 |
103 | export const socialLogin = user => {
104 | return fetch(`${process.env.REACT_APP_API_URL}/social-login/`, {
105 | method: 'POST',
106 | headers: {
107 | Accept: 'application/json',
108 | 'Content-Type': 'application/json'
109 | },
110 | // credentials: "include", // works only in the same origin
111 | body: JSON.stringify(user)
112 | })
113 | .then(response => {
114 | console.log('signin response: ', response);
115 | return response.json();
116 | })
117 | .catch(err => console.log(err));
118 | };
119 |
--------------------------------------------------------------------------------
/src/core/Home.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Posts from "../post/Posts";
3 |
4 | const Home = () => (
5 |
6 |
7 |
8 | React Node MongoDB Social Network App
9 |
10 |
11 | Node API, React web app, Authentication, User Profile, Follow/Unfollow,
12 | Like/Unlike, Comments, Social Login and more
13 |
14 |
15 |
18 |
19 | );
20 |
21 | export default Home;
22 |
--------------------------------------------------------------------------------
/src/core/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Link, withRouter } from 'react-router-dom';
3 | import { signout, isAuthenticated } from '../auth';
4 |
5 | const isActive = (history, path) => {
6 | if (history.location.pathname === path) return { color: '#ff9900' };
7 | else return { color: '#ffffff' };
8 | };
9 |
10 | const Menu = ({ history }) => (
11 |
12 |
13 | -
14 |
15 | Home
16 |
17 |
18 |
19 | -
20 |
24 | Users
25 |
26 |
27 |
28 | -
29 |
30 | Create Post
31 |
32 |
33 |
34 | {!isAuthenticated() && (
35 |
36 | -
37 |
38 | Sign In
39 |
40 |
41 | -
42 |
43 | Sign Up
44 |
45 |
46 |
47 | )}
48 |
49 | {isAuthenticated() && isAuthenticated().user.role === 'admin' && (
50 | -
51 |
52 | Admin
53 |
54 |
55 | )}
56 |
57 | {isAuthenticated() && (
58 |
59 | -
60 |
61 | Find People
62 |
63 |
64 |
65 | -
66 |
71 | {`${isAuthenticated().user.name}'s profile`}
72 |
73 |
74 |
75 | -
76 | signout(() => history.push('/'))}
80 | >
81 | Sign Out
82 |
83 |
84 |
85 | )}
86 |
87 |
88 | );
89 |
90 | export default withRouter(Menu);
91 |
--------------------------------------------------------------------------------
/src/images/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/react-front/be63af731d2c353460f6c3e12d54654855e617f2/src/images/avatar.jpg
--------------------------------------------------------------------------------
/src/images/mountains.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaloraat/react-front/be63af731d2c353460f6c3e12d54654855e617f2/src/images/mountains.jpg
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 |
5 | ReactDOM.render(, document.getElementById("root"));
6 |
--------------------------------------------------------------------------------
/src/post/Comment.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { comment, uncomment } from "./apiPost";
3 | import { isAuthenticated } from "../auth";
4 | import { Link } from "react-router-dom";
5 | import DefaultProfile from "../images/avatar.jpg";
6 |
7 | class Comment extends Component {
8 | state = {
9 | text: "",
10 | error: ""
11 | };
12 |
13 | handleChange = event => {
14 | this.setState({ error: "" });
15 | this.setState({ text: event.target.value });
16 | };
17 |
18 | isValid = () => {
19 | const { text } = this.state;
20 | if (!text.length > 0 || text.length > 150) {
21 | this.setState({
22 | error:
23 | "Comment should not be empty and less than 150 characters long"
24 | });
25 | return false;
26 | }
27 | return true;
28 | };
29 |
30 | addComment = e => {
31 | e.preventDefault();
32 |
33 | if (!isAuthenticated()) {
34 | this.setState({ error: "Please signin to leave a comment" });
35 | return false;
36 | }
37 |
38 | if (this.isValid()) {
39 | const userId = isAuthenticated().user._id;
40 | const token = isAuthenticated().token;
41 | const postId = this.props.postId;
42 |
43 | comment(userId, token, postId, { text: this.state.text }).then(
44 | data => {
45 | if (data.error) {
46 | console.log(data.error);
47 | } else {
48 | this.setState({ text: "" });
49 | // dispatch fresh list of coments to parent (SinglePost)
50 | this.props.updateComments(data.comments);
51 | }
52 | }
53 | );
54 | }
55 | };
56 |
57 | deleteComment = comment => {
58 | const userId = isAuthenticated().user._id;
59 | const token = isAuthenticated().token;
60 | const postId = this.props.postId;
61 |
62 | uncomment(userId, token, postId, comment).then(data => {
63 | if (data.error) {
64 | console.log(data.error);
65 | } else {
66 | this.props.updateComments(data.comments);
67 | }
68 | });
69 | };
70 |
71 | deleteConfirmed = comment => {
72 | let answer = window.confirm(
73 | "Are you sure you want to delete your comment?"
74 | );
75 | if (answer) {
76 | this.deleteComment(comment);
77 | }
78 | };
79 |
80 | render() {
81 | const { comments } = this.props;
82 | const { error } = this.state;
83 |
84 | return (
85 |
86 |
Leave a comment
87 |
88 |
102 |
103 |
107 | {error}
108 |
109 |
110 |
111 |
{comments.length} Comments
112 |
113 | {comments.map((comment, i) => (
114 |
115 |
116 |
117 |
![]()
126 | (i.target.src = `${DefaultProfile}`)
127 | }
128 | src={`${
129 | process.env.REACT_APP_API_URL
130 | }/user/photo/${comment.postedBy._id}`}
131 | alt={comment.postedBy.name}
132 | />
133 |
134 |
135 |
{comment.text}
136 |
137 | Posted by{" "}
138 |
141 | {comment.postedBy.name}{" "}
142 |
143 | on{" "}
144 | {new Date(
145 | comment.created
146 | ).toDateString()}
147 |
148 | {isAuthenticated().user &&
149 | isAuthenticated().user._id ===
150 | comment.postedBy._id && (
151 | <>
152 |
154 | this.deleteConfirmed(
155 | comment
156 | )
157 | }
158 | className="text-danger float-right mr-1"
159 | >
160 | Remove
161 |
162 | >
163 | )}
164 |
165 |
166 |
167 |
168 |
169 | ))}
170 |
171 |
172 | );
173 | }
174 | }
175 |
176 | export default Comment;
177 |
--------------------------------------------------------------------------------
/src/post/EditPost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { singlePost, update } from "./apiPost";
3 | import { isAuthenticated } from "../auth";
4 | import { Redirect } from "react-router-dom";
5 | import DefaultPost from "../images/mountains.jpg";
6 |
7 | class EditPost extends Component {
8 | constructor() {
9 | super();
10 | this.state = {
11 | id: "",
12 | title: "",
13 | body: "",
14 | redirectToProfile: false,
15 | error: "",
16 | fileSize: 0,
17 | loading: false
18 | };
19 | }
20 |
21 | init = postId => {
22 | singlePost(postId).then(data => {
23 | if (data.error) {
24 | this.setState({ redirectToProfile: true });
25 | } else {
26 | this.setState({
27 | id: data.postedBy._id,
28 | title: data.title,
29 | body: data.body,
30 | error: ""
31 | });
32 | }
33 | });
34 | };
35 |
36 | componentDidMount() {
37 | this.postData = new FormData();
38 | const postId = this.props.match.params.postId;
39 | this.init(postId);
40 | }
41 |
42 | isValid = () => {
43 | const { title, body, fileSize } = this.state;
44 | if (fileSize > 1000000) {
45 | this.setState({
46 | error: "File size should be less than 100kb",
47 | loading: false
48 | });
49 | return false;
50 | }
51 | if (title.length === 0 || body.length === 0) {
52 | this.setState({ error: "All fields are required", loading: false });
53 | return false;
54 | }
55 | return true;
56 | };
57 |
58 | handleChange = name => event => {
59 | this.setState({ error: "" });
60 | const value =
61 | name === "photo" ? event.target.files[0] : event.target.value;
62 |
63 | const fileSize = name === "photo" ? event.target.files[0].size : 0;
64 | this.postData.set(name, value);
65 | this.setState({ [name]: value, fileSize });
66 | };
67 |
68 | clickSubmit = event => {
69 | event.preventDefault();
70 | this.setState({ loading: true });
71 |
72 | if (this.isValid()) {
73 | const postId = this.props.match.params.postId;
74 | const token = isAuthenticated().token;
75 |
76 | update(postId, token, this.postData).then(data => {
77 | if (data.error) this.setState({ error: data.error });
78 | else {
79 | this.setState({
80 | loading: false,
81 | title: "",
82 | body: "",
83 | redirectToProfile: true
84 | });
85 | }
86 | });
87 | }
88 | };
89 |
90 | editPostForm = (title, body) => (
91 |
128 | );
129 |
130 | render() {
131 | const {
132 | id,
133 | title,
134 | body,
135 | redirectToProfile,
136 | error,
137 | loading
138 | } = this.state;
139 |
140 | if (redirectToProfile) {
141 | return ;
142 | }
143 |
144 | return (
145 |
146 |
{title}
147 |
148 |
152 | {error}
153 |
154 |
155 | {loading ? (
156 |
157 |
Loading...
158 |
159 | ) : (
160 | ""
161 | )}
162 |
163 |

(i.target.src = `${DefaultPost}`)}
170 | alt={title}
171 | />
172 |
173 | {isAuthenticated().user.role === "admin" &&
174 | this.editPostForm(title, body)}
175 |
176 | {isAuthenticated().user._id === id &&
177 | this.editPostForm(title, body)}
178 |
179 | );
180 | }
181 | }
182 |
183 | export default EditPost;
184 |
--------------------------------------------------------------------------------
/src/post/NewPost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { isAuthenticated } from "../auth";
3 | import { create } from "./apiPost";
4 | import { Redirect } from "react-router-dom";
5 |
6 | class NewPost extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | title: "",
11 | body: "",
12 | photo: "",
13 | error: "",
14 | user: {},
15 | fileSize: 0,
16 | loading: false,
17 | redirectToProfile: false
18 | };
19 | }
20 |
21 | componentDidMount() {
22 | this.postData = new FormData();
23 | this.setState({ user: isAuthenticated().user });
24 | }
25 |
26 | isValid = () => {
27 | const { title, body, fileSize } = this.state;
28 | if (fileSize > 100000) {
29 | this.setState({
30 | error: "File size should be less than 100kb",
31 | loading: false
32 | });
33 | return false;
34 | }
35 | if (title.length === 0 || body.length === 0) {
36 | this.setState({ error: "All fields are required", loading: false });
37 | return false;
38 | }
39 | return true;
40 | };
41 |
42 | handleChange = name => event => {
43 | this.setState({ error: "" });
44 | const value =
45 | name === "photo" ? event.target.files[0] : event.target.value;
46 |
47 | const fileSize = name === "photo" ? event.target.files[0].size : 0;
48 | this.postData.set(name, value);
49 | this.setState({ [name]: value, fileSize });
50 | };
51 |
52 | clickSubmit = event => {
53 | event.preventDefault();
54 | this.setState({ loading: true });
55 |
56 | if (this.isValid()) {
57 | const userId = isAuthenticated().user._id;
58 | const token = isAuthenticated().token;
59 |
60 | create(userId, token, this.postData).then(data => {
61 | if (data.error) this.setState({ error: data.error });
62 | else {
63 | this.setState({
64 | loading: false,
65 | title: "",
66 | body: "",
67 | redirectToProfile: true
68 | });
69 | }
70 | });
71 | }
72 | };
73 |
74 | newPostForm = (title, body) => (
75 |
112 | );
113 |
114 | render() {
115 | const {
116 | title,
117 | body,
118 | photo,
119 | user,
120 | error,
121 | loading,
122 | redirectToProfile
123 | } = this.state;
124 |
125 | if (redirectToProfile) {
126 | return ;
127 | }
128 |
129 | return (
130 |
131 |
Create a new post
132 |
136 | {error}
137 |
138 |
139 | {loading ? (
140 |
141 |
Loading...
142 |
143 | ) : (
144 | ""
145 | )}
146 |
147 | {this.newPostForm(title, body)}
148 |
149 | );
150 | }
151 | }
152 |
153 | export default NewPost;
154 |
--------------------------------------------------------------------------------
/src/post/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { list } from "./apiPost";
3 | import DefaultPost from "../images/mountains.jpg";
4 | import { Link } from "react-router-dom";
5 |
6 | class Posts extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | posts: [],
11 | page: 1
12 | };
13 | }
14 |
15 | loadPosts = page => {
16 | list(page).then(data => {
17 | if (data.error) {
18 | console.log(data.error);
19 | } else {
20 | console.log(data);
21 | this.setState({ posts: data });
22 | }
23 | });
24 | };
25 |
26 | componentDidMount() {
27 | this.loadPosts(this.state.page);
28 | }
29 |
30 | loadMore = number => {
31 | this.setState({ page: this.state.page + number });
32 | this.loadPosts(this.state.page + number);
33 | };
34 |
35 | loadLess = number => {
36 | this.setState({ page: this.state.page - number });
37 | this.loadPosts(this.state.page - number);
38 | };
39 |
40 | renderPosts = posts => {
41 | return (
42 |
43 | {posts.map((post, i) => {
44 | const posterId = post.postedBy ? `/user/${post.postedBy._id}` : "";
45 | const posterName = post.postedBy ? post.postedBy.name : " Unknown";
46 |
47 | if (!post.photo) {
48 | return;
49 | }
50 |
51 | return (
52 |
53 |
54 |

(i.target.src = `${DefaultPost}`)}
58 | className="img-thunbnail mb-3"
59 | style={{ height: "200px", width: "100%" }}
60 | />
61 |
{post.title}
62 |
{post.body.substring(0, 100)}
63 |
64 |
65 | Posted by {posterName}
66 | on {new Date(post.created).toDateString()}
67 |
68 |
72 | Read more
73 |
74 |
75 |
76 | );
77 | })}
78 |
79 | );
80 | };
81 |
82 | render() {
83 | const { posts, page } = this.state;
84 | return (
85 |
86 |
87 | {!posts.length ? "No more posts!" : "Recent Posts"}
88 |
89 |
90 | {this.renderPosts(posts)}
91 |
92 | {page > 1 ? (
93 |
99 | ) : (
100 | ""
101 | )}
102 |
103 | {posts.length ? (
104 |
110 | ) : (
111 | ""
112 | )}
113 |
114 | );
115 | }
116 | }
117 |
118 | export default Posts;
119 |
--------------------------------------------------------------------------------
/src/post/SinglePost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { singlePost, remove, like, unlike } from './apiPost';
3 | import DefaultPost from '../images/mountains.jpg';
4 | import { Link, Redirect } from 'react-router-dom';
5 | import { isAuthenticated } from '../auth';
6 | import Comment from './Comment';
7 |
8 | class SinglePost extends Component {
9 | state = {
10 | post: '',
11 | redirectToHome: false,
12 | redirectToSignin: false,
13 | like: false,
14 | likes: 0,
15 | comments: []
16 | };
17 |
18 | checkLike = likes => {
19 | const userId = isAuthenticated() && isAuthenticated().user._id;
20 | let match = likes.indexOf(userId) !== -1;
21 | return match;
22 | };
23 |
24 | componentDidMount = () => {
25 | const postId = this.props.match.params.postId;
26 | singlePost(postId).then(data => {
27 | if (data.error) {
28 | console.log(data.error);
29 | } else {
30 | console.log('COMMENTING USER IN SIGNEL POST', data);
31 | this.setState({
32 | post: data,
33 | likes: data.likes.length,
34 | like: this.checkLike(data.likes),
35 | comments: data.comments
36 | });
37 | }
38 | });
39 | };
40 |
41 | updateComments = comments => {
42 | this.setState({ comments });
43 | };
44 |
45 | likeToggle = () => {
46 | if (!isAuthenticated()) {
47 | this.setState({ redirectToSignin: true });
48 | return false;
49 | }
50 | let callApi = this.state.like ? unlike : like;
51 | const userId = isAuthenticated().user._id;
52 | const postId = this.state.post._id;
53 | const token = isAuthenticated().token;
54 |
55 | callApi(userId, token, postId).then(data => {
56 | if (data.error) {
57 | console.log(data.error);
58 | } else {
59 | this.setState({
60 | like: !this.state.like,
61 | likes: data.likes.length
62 | });
63 | }
64 | });
65 | };
66 |
67 | deletePost = () => {
68 | const postId = this.props.match.params.postId;
69 | const token = isAuthenticated().token;
70 | remove(postId, token).then(data => {
71 | if (data.error) {
72 | console.log(data.error);
73 | } else {
74 | this.setState({ redirectToHome: true });
75 | }
76 | });
77 | };
78 |
79 | deleteConfirmed = () => {
80 | let answer = window.confirm('Are you sure you want to delete your post?');
81 | if (answer) {
82 | this.deletePost();
83 | }
84 | };
85 |
86 | renderPost = post => {
87 | const posterId = post.postedBy ? `/user/${post.postedBy._id}` : '';
88 | const posterName = post.postedBy ? post.postedBy.name : ' Unknown';
89 |
90 | const { like, likes } = this.state;
91 |
92 | return (
93 |
94 |

(i.target.src = `${DefaultPost}`)}
98 | className="img-thunbnail mb-3"
99 | style={{
100 | height: '300px',
101 | width: '100%',
102 | objectFit: 'cover'
103 | }}
104 | />
105 |
106 | {like ? (
107 |
108 | {' '}
112 | {likes} Like
113 |
114 | ) : (
115 |
116 | {' '}
120 | {likes} Like
121 |
122 | )}
123 |
124 |
{post.body}
125 |
126 |
127 | Posted by {posterName}
128 | on {new Date(post.created).toDateString()}
129 |
130 |
131 |
132 | Back to posts
133 |
134 |
135 | {isAuthenticated().user && isAuthenticated().user._id === post.postedBy._id && (
136 | <>
137 |
138 | Update Post
139 |
140 |
143 | >
144 | )}
145 |
146 |
147 | {isAuthenticated().user && isAuthenticated().user.role === 'admin' && (
148 |
149 |
150 |
Admin
151 |
Edit/Delete as an Admin
152 |
156 | Update Post
157 |
158 |
161 |
162 |
163 | )}
164 |
165 |
166 |
167 | );
168 | };
169 |
170 | render() {
171 | const { post, redirectToHome, redirectToSignin, comments } = this.state;
172 |
173 | if (redirectToHome) {
174 | return ;
175 | } else if (redirectToSignin) {
176 | return ;
177 | }
178 |
179 | return (
180 |
181 |
{post.title}
182 |
183 | {!post ? (
184 |
185 |
Loading...
186 |
187 | ) : (
188 | this.renderPost(post)
189 | )}
190 |
191 |
192 |
193 | );
194 | }
195 | }
196 |
197 | export default SinglePost;
198 |
--------------------------------------------------------------------------------
/src/post/apiPost.js:
--------------------------------------------------------------------------------
1 | export const create = (userId, token, post) => {
2 | return fetch(`${process.env.REACT_APP_API_URL}/post/new/${userId}`, {
3 | method: "POST",
4 | headers: {
5 | Accept: "application/json",
6 | Authorization: `Bearer ${token}`
7 | },
8 | body: post
9 | })
10 | .then(response => {
11 | return response.json();
12 | })
13 | .catch(err => console.log(err));
14 | };
15 |
16 | // export const list = () => {
17 | // return fetch(`${process.env.REACT_APP_API_URL}/posts`, {
18 | // method: "GET"
19 | // })
20 | // .then(response => {
21 | // return response.json();
22 | // })
23 | // .catch(err => console.log(err));
24 | // };
25 |
26 | // with pagination
27 | export const list = page => {
28 | return fetch(`${process.env.REACT_APP_API_URL}/posts/?page=${page}`, {
29 | method: "GET"
30 | })
31 | .then(response => {
32 | return response.json();
33 | })
34 | .catch(err => console.log(err));
35 | };
36 |
37 | export const singlePost = postId => {
38 | return fetch(`${process.env.REACT_APP_API_URL}/post/${postId}`, {
39 | method: "GET"
40 | })
41 | .then(response => {
42 | return response.json();
43 | })
44 | .catch(err => console.log(err));
45 | };
46 |
47 | export const listByUser = (userId, token) => {
48 | return fetch(`${process.env.REACT_APP_API_URL}/posts/by/${userId}`, {
49 | method: "GET",
50 | headers: {
51 | Accept: "application/json",
52 | "Content-Type": "application/json",
53 | Authorization: `Bearer ${token}`
54 | }
55 | })
56 | .then(response => {
57 | return response.json();
58 | })
59 | .catch(err => console.log(err));
60 | };
61 |
62 | export const remove = (postId, token) => {
63 | return fetch(`${process.env.REACT_APP_API_URL}/post/${postId}`, {
64 | method: "DELETE",
65 | headers: {
66 | Accept: "application/json",
67 | "Content-Type": "application/json",
68 | Authorization: `Bearer ${token}`
69 | }
70 | })
71 | .then(response => {
72 | return response.json();
73 | })
74 | .catch(err => console.log(err));
75 | };
76 |
77 | export const update = (postId, token, post) => {
78 | console.log(postId, token, post);
79 | return fetch(`${process.env.REACT_APP_API_URL}/post/${postId}`, {
80 | method: "PUT",
81 | headers: {
82 | Accept: "application/json",
83 | Authorization: `Bearer ${token}`
84 | },
85 | body: post
86 | })
87 | .then(response => {
88 | return response.json();
89 | })
90 | .catch(err => console.log(err));
91 | };
92 |
93 | export const like = (userId, token, postId) => {
94 | return fetch(`${process.env.REACT_APP_API_URL}/post/like`, {
95 | method: "PUT",
96 | headers: {
97 | Accept: "application/json",
98 | "Content-Type": "application/json",
99 | Authorization: `Bearer ${token}`
100 | },
101 | body: JSON.stringify({ userId, postId })
102 | })
103 | .then(response => {
104 | return response.json();
105 | })
106 | .catch(err => console.log(err));
107 | };
108 |
109 | export const unlike = (userId, token, postId) => {
110 | return fetch(`${process.env.REACT_APP_API_URL}/post/unlike`, {
111 | method: "PUT",
112 | headers: {
113 | Accept: "application/json",
114 | "Content-Type": "application/json",
115 | Authorization: `Bearer ${token}`
116 | },
117 | body: JSON.stringify({ userId, postId })
118 | })
119 | .then(response => {
120 | return response.json();
121 | })
122 | .catch(err => console.log(err));
123 | };
124 |
125 | export const comment = (userId, token, postId, comment) => {
126 | return fetch(`${process.env.REACT_APP_API_URL}/post/comment`, {
127 | method: "PUT",
128 | headers: {
129 | Accept: "application/json",
130 | "Content-Type": "application/json",
131 | Authorization: `Bearer ${token}`
132 | },
133 | body: JSON.stringify({ userId, postId, comment })
134 | })
135 | .then(response => {
136 | return response.json();
137 | })
138 | .catch(err => console.log(err));
139 | };
140 |
141 | export const uncomment = (userId, token, postId, comment) => {
142 | return fetch(`${process.env.REACT_APP_API_URL}/post/uncomment`, {
143 | method: "PUT",
144 | headers: {
145 | Accept: "application/json",
146 | "Content-Type": "application/json",
147 | Authorization: `Bearer ${token}`
148 | },
149 | body: JSON.stringify({ userId, postId, comment })
150 | })
151 | .then(response => {
152 | return response.json();
153 | })
154 | .catch(err => console.log(err));
155 | };
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/user/DeleteUser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import { isAuthenticated } from '../auth';
4 | import { remove } from './apiUser';
5 | import { signout } from '../auth';
6 |
7 | class DeleteUser extends Component {
8 | state = {
9 | redirect: false
10 | };
11 |
12 | deleteAccount = () => {
13 | const token = isAuthenticated().token;
14 | const userId = this.props.userId;
15 | console.log('userId in deleteAccount ', userId);
16 | remove(userId, token).then(data => {
17 | if (data.error) {
18 | console.log(data.error);
19 | } else {
20 | // signout user
21 | signout(() => console.log('User is deleted'));
22 | // redirect
23 | this.setState({ redirect: true });
24 | }
25 | });
26 | };
27 |
28 | deleteConfirmed = () => {
29 | let answer = window.confirm('Are you sure you want to delete your account?');
30 | if (answer) {
31 | this.deleteAccount();
32 | }
33 | };
34 |
35 | render() {
36 | if (this.state.redirect) {
37 | return ;
38 | }
39 | return (
40 |
43 | );
44 | }
45 | }
46 |
47 | export default DeleteUser;
48 |
--------------------------------------------------------------------------------
/src/user/EditProfile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { isAuthenticated } from "../auth";
3 | import { read, update, updateUser } from "./apiUser";
4 | import { Redirect } from "react-router-dom";
5 | import DefaultProfile from "../images/avatar.jpg";
6 |
7 | class EditProfile extends Component {
8 | constructor() {
9 | super();
10 | this.state = {
11 | id: "",
12 | name: "",
13 | email: "",
14 | password: "",
15 | redirectToProfile: false,
16 | error: "",
17 | fileSize: 0,
18 | loading: false,
19 | about: ""
20 | };
21 | }
22 |
23 | init = userId => {
24 | const token = isAuthenticated().token;
25 | read(userId, token).then(data => {
26 | if (data.error) {
27 | this.setState({ redirectToProfile: true });
28 | } else {
29 | this.setState({
30 | id: data._id,
31 | name: data.name,
32 | email: data.email,
33 | error: "",
34 | about: data.about
35 | });
36 | }
37 | });
38 | };
39 |
40 | componentDidMount() {
41 | this.userData = new FormData();
42 | const userId = this.props.match.params.userId;
43 | this.init(userId);
44 | }
45 |
46 | isValid = () => {
47 | const { name, email, password, fileSize } = this.state;
48 | if (fileSize > 1000000) {
49 | this.setState({
50 | error: "File size should be less than 100kb",
51 | loading: false
52 | });
53 | return false;
54 | }
55 | if (name.length === 0) {
56 | this.setState({ error: "Name is required", loading: false });
57 | return false;
58 | }
59 | // email@domain.com
60 | if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
61 | this.setState({
62 | error: "A valid Email is required",
63 | loading: false
64 | });
65 | return false;
66 | }
67 | if (password.length >= 1 && password.length <= 5) {
68 | this.setState({
69 | error: "Password must be at least 6 characters long",
70 | loading: false
71 | });
72 | return false;
73 | }
74 | return true;
75 | };
76 |
77 | handleChange = name => event => {
78 | this.setState({ error: "" });
79 | const value = name === "photo" ? event.target.files[0] : event.target.value;
80 |
81 | const fileSize = name === "photo" ? event.target.files[0].size : 0;
82 | this.userData.set(name, value);
83 | this.setState({ [name]: value, fileSize });
84 | };
85 |
86 | clickSubmit = event => {
87 | event.preventDefault();
88 | this.setState({ loading: true });
89 |
90 | if (this.isValid()) {
91 | const userId = this.props.match.params.userId;
92 | const token = isAuthenticated().token;
93 |
94 | update(userId, token, this.userData).then(data => {
95 | if (data.error) {
96 | this.setState({ error: data.error });
97 | } else if (isAuthenticated().user.role === "admin") {
98 | this.setState({
99 | redirectToProfile: true
100 | });
101 | } else {
102 | updateUser(data, () => {
103 | this.setState({
104 | redirectToProfile: true
105 | });
106 | });
107 | }
108 | });
109 | }
110 | };
111 |
112 | signupForm = (name, email, password, about) => (
113 |
165 | );
166 |
167 | render() {
168 | const {
169 | id,
170 | name,
171 | email,
172 | password,
173 | redirectToProfile,
174 | error,
175 | loading,
176 | about
177 | } = this.state;
178 |
179 | if (redirectToProfile) {
180 | return ;
181 | }
182 |
183 | const photoUrl = id
184 | ? `${
185 | process.env.REACT_APP_API_URL
186 | }/user/photo/${id}?${new Date().getTime()}`
187 | : DefaultProfile;
188 |
189 | return (
190 |
191 |
Edit Profile
192 |
196 | {error}
197 |
198 |
199 | {loading ? (
200 |
201 |
Loading...
202 |
203 | ) : (
204 | ""
205 | )}
206 |
207 |

(i.target.src = `${DefaultProfile}`)}
212 | alt={name}
213 | />
214 |
215 | {isAuthenticated().user.role === "admin" &&
216 | this.signupForm(name, email, password, about)}
217 |
218 | {isAuthenticated().user._id === id &&
219 | this.signupForm(name, email, password, about)}
220 |
221 | );
222 | }
223 | }
224 |
225 | export default EditProfile;
226 |
--------------------------------------------------------------------------------
/src/user/FindPeople.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { findPeople, follow } from "./apiUser";
3 | import DefaultProfile from "../images/avatar.jpg";
4 | import { Link } from "react-router-dom";
5 | import { isAuthenticated } from "../auth";
6 |
7 | class FindPeople extends Component {
8 | constructor() {
9 | super();
10 | this.state = {
11 | users: [],
12 | error: "",
13 | open: false
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | const userId = isAuthenticated().user._id;
19 | const token = isAuthenticated().token;
20 |
21 | findPeople(userId, token).then(data => {
22 | if (data.error) {
23 | console.log(data.error);
24 | } else {
25 | this.setState({ users: data });
26 | }
27 | });
28 | }
29 |
30 | clickFollow = (user, i) => {
31 | const userId = isAuthenticated().user._id;
32 | const token = isAuthenticated().token;
33 |
34 | follow(userId, token, user._id).then(data => {
35 | if (data.error) {
36 | this.setState({ error: data.error });
37 | } else {
38 | let toFollow = this.state.users;
39 | toFollow.splice(i, 1);
40 | this.setState({
41 | users: toFollow,
42 | open: true,
43 | followMessage: `Following ${user.name}`
44 | });
45 | }
46 | });
47 | };
48 |
49 | renderUsers = users => (
50 |
51 | {users.map((user, i) => (
52 |
53 |

(i.target.src = `${DefaultProfile}`)}
60 | alt={user.name}
61 | />
62 |
63 |
{user.name}
64 |
{user.email}
65 |
69 | View Profile
70 |
71 |
72 |
78 |
79 |
80 | ))}
81 |
82 | );
83 |
84 | render() {
85 | const { users, open, followMessage } = this.state;
86 | return (
87 |
88 |
Find People
89 |
90 | {open && (
91 |
{followMessage}
92 | )}
93 |
94 | {this.renderUsers(users)}
95 |
96 | );
97 | }
98 | }
99 |
100 | export default FindPeople;
101 |
--------------------------------------------------------------------------------
/src/user/FollowProfileButton.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { follow, unfollow } from "./apiUser";
3 |
4 | class FollowProfileButton extends Component {
5 | followClick = () => {
6 | this.props.onButtonClick(follow);
7 | };
8 |
9 | unfollowClick = () => {
10 | this.props.onButtonClick(unfollow);
11 | };
12 |
13 | render() {
14 | return (
15 |
16 | {!this.props.following ? (
17 |
23 | ) : (
24 |
30 | )}
31 |
32 | );
33 | }
34 | }
35 |
36 | export default FollowProfileButton;
37 |
--------------------------------------------------------------------------------
/src/user/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { forgotPassword } from "../auth";
3 |
4 | class ForgotPassword extends Component {
5 | state = {
6 | email: "",
7 | message: "",
8 | error: ""
9 | };
10 |
11 | forgotPassword = e => {
12 | e.preventDefault();
13 | this.setState({ message: "", error: "" });
14 | forgotPassword(this.state.email).then(data => {
15 | if (data.error) {
16 | console.log(data.error);
17 | this.setState({ error: data.error });
18 | } else {
19 | console.log(data.message);
20 | this.setState({ message: data.message });
21 | }
22 | });
23 | };
24 |
25 | render() {
26 | return (
27 |
28 |
Ask for Password Reset
29 |
30 | {this.state.message && (
31 |
{this.state.message}
32 | )}
33 | {this.state.error && (
34 |
{this.state.error}
35 | )}
36 |
37 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default ForgotPassword;
68 |
--------------------------------------------------------------------------------
/src/user/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { isAuthenticated } from '../auth';
3 | import { Redirect, Link } from 'react-router-dom';
4 | import { read } from './apiUser';
5 | import DefaultProfile from '../images/avatar.jpg';
6 | import DeleteUser from './DeleteUser';
7 | import FollowProfileButton from './FollowProfileButton';
8 | import ProfileTabs from './ProfileTabs';
9 | import { listByUser } from '../post/apiPost';
10 |
11 | class Profile extends Component {
12 | constructor() {
13 | super();
14 | this.state = {
15 | user: { following: [], followers: [] },
16 | redirectToSignin: false,
17 | following: false,
18 | error: '',
19 | posts: []
20 | };
21 | }
22 |
23 | // check follow
24 | checkFollow = user => {
25 | const jwt = isAuthenticated();
26 | const match = user.followers.find(follower => {
27 | // one id has many other ids (followers) and vice versa
28 | return follower._id === jwt.user._id;
29 | });
30 | return match;
31 | };
32 |
33 | clickFollowButton = callApi => {
34 | const userId = isAuthenticated().user._id;
35 | const token = isAuthenticated().token;
36 |
37 | callApi(userId, token, this.state.user._id).then(data => {
38 | if (data.error) {
39 | this.setState({ error: data.error });
40 | } else {
41 | this.setState({ user: data, following: !this.state.following });
42 | }
43 | });
44 | };
45 |
46 | init = userId => {
47 | const token = isAuthenticated().token;
48 | read(userId, token).then(data => {
49 | if (data.error) {
50 | this.setState({ redirectToSignin: true });
51 | } else {
52 | let following = this.checkFollow(data);
53 | this.setState({ user: data, following });
54 | this.loadPosts(data._id);
55 | }
56 | });
57 | };
58 |
59 | loadPosts = userId => {
60 | const token = isAuthenticated().token;
61 | listByUser(userId, token).then(data => {
62 | if (data.error) {
63 | console.log(data.error);
64 | } else {
65 | this.setState({ posts: data });
66 | }
67 | });
68 | };
69 |
70 | componentDidMount() {
71 | const userId = this.props.match.params.userId;
72 | this.init(userId);
73 | }
74 |
75 | componentWillReceiveProps(props) {
76 | const userId = props.match.params.userId;
77 | this.init(userId);
78 | }
79 |
80 | render() {
81 | const { redirectToSignin, user, posts } = this.state;
82 | if (redirectToSignin) return ;
83 |
84 | const photoUrl = user._id
85 | ? `${process.env.REACT_APP_API_URL}/user/photo/${user._id}?${new Date().getTime()}`
86 | : DefaultProfile;
87 |
88 | return (
89 |
90 |
Profile
91 |
92 |
93 |

(i.target.src = `${DefaultProfile}`)}
98 | alt={user.name}
99 | />
100 |
101 |
102 |
103 |
104 |
Hello {user.name}
105 |
Email: {user.email}
106 |
{`Joined ${new Date(user.created).toDateString()}`}
107 |
108 |
109 | {isAuthenticated().user && isAuthenticated().user._id === user._id ? (
110 |
111 |
112 | Create Post
113 |
114 |
115 |
116 | Edit Profile
117 |
118 |
119 |
120 | ) : (
121 |
122 | )}
123 |
124 |
125 | {isAuthenticated().user && isAuthenticated().user.role === 'admin' && (
126 |
127 |
128 |
Admin
129 |
Edit/Delete as an Admin
130 |
131 | Edit Profile
132 |
133 |
134 |
135 |
136 | )}
137 |
138 |
139 |
140 |
141 |
142 |
143 |
{user.about}
144 |
145 |
146 |
147 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | export default Profile;
155 |
--------------------------------------------------------------------------------
/src/user/ProfileTabs.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import DefaultProfile from "../images/avatar.jpg";
4 |
5 | class ProfileTabs extends Component {
6 | render() {
7 | const { following, followers, posts } = this.props;
8 | return (
9 |
10 |
11 |
12 |
13 | {followers.length} Followers
14 |
15 |
16 | {followers.map((person, i) => (
17 |
18 |
19 |
20 |
![]()
29 | (i.target.src = `${DefaultProfile}`)
30 | }
31 | src={`${
32 | process.env.REACT_APP_API_URL
33 | }/user/photo/${person._id}`}
34 | alt={person.name}
35 | />
36 |
37 |
38 | {person.name}
39 |
40 |
41 |
42 |
43 |
44 | ))}
45 |
46 |
47 |
48 |
49 | {following.length} Following
50 |
51 |
52 | {following.map((person, i) => (
53 |
54 |
55 |
56 |
![]()
65 | (i.target.src = `${DefaultProfile}`)
66 | }
67 | src={`${
68 | process.env.REACT_APP_API_URL
69 | }/user/photo/${person._id}`}
70 | alt={person.name}
71 | />
72 |
73 |
74 | {person.name}
75 |
76 |
77 |
78 |
79 |
80 | ))}
81 |
82 |
83 |
84 |
{posts.length} Posts
85 |
86 | {posts.map((post, i) => (
87 |
96 | ))}
97 |
98 |
99 |
100 | );
101 | }
102 | }
103 |
104 | export default ProfileTabs;
105 |
--------------------------------------------------------------------------------
/src/user/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { resetPassword } from "../auth";
3 |
4 | class ResetPassword extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | newPassword: "",
9 | message: "",
10 | error: ""
11 | };
12 | }
13 |
14 | resetPassword = e => {
15 | e.preventDefault();
16 | this.setState({ message: "", error: "" });
17 |
18 | resetPassword({
19 | newPassword: this.state.newPassword,
20 | resetPasswordLink: this.props.match.params.resetPasswordToken
21 | }).then(data => {
22 | if (data.error) {
23 | console.log(data.error);
24 | this.setState({ error: data.error, newPassword: "" });
25 | } else {
26 | console.log(data.message);
27 | this.setState({ message: data.message, newPassword: "" });
28 | }
29 | });
30 | };
31 |
32 | render() {
33 | return (
34 |
35 |
Reset your Password
36 |
37 | {this.state.message && (
38 |
{this.state.message}
39 | )}
40 | {this.state.error && (
41 |
{this.state.error}
42 | )}
43 |
44 |
71 |
72 | );
73 | }
74 | }
75 |
76 | export default ResetPassword;
77 |
--------------------------------------------------------------------------------
/src/user/Signin.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, Redirect } from "react-router-dom";
3 | import { signin, authenticate } from "../auth";
4 | import SocialLogin from "./SocialLogin";
5 |
6 | class Signin extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | email: "",
11 | password: "",
12 | error: "",
13 | redirectToReferer: false,
14 | loading: false,
15 | recaptcha: false
16 | };
17 | }
18 |
19 | handleChange = name => event => {
20 | this.setState({ error: "" });
21 | this.setState({ [name]: event.target.value });
22 | };
23 |
24 | recaptchaHandler = e => {
25 | this.setState({ error: "" });
26 | let userDay = e.target.value.toLowerCase();
27 | let dayCount;
28 |
29 | if (userDay === "sunday") {
30 | dayCount = 0;
31 | } else if (userDay === "monday") {
32 | dayCount = 1;
33 | } else if (userDay === "tuesday") {
34 | dayCount = 2;
35 | } else if (userDay === "wednesday") {
36 | dayCount = 3;
37 | } else if (userDay === "thursday") {
38 | dayCount = 4;
39 | } else if (userDay === "friday") {
40 | dayCount = 5;
41 | } else if (userDay === "saturday") {
42 | dayCount = 6;
43 | }
44 |
45 | if (dayCount === new Date().getDay()) {
46 | this.setState({ recaptcha: true });
47 | return true;
48 | } else {
49 | this.setState({
50 | recaptcha: false
51 | });
52 | return false;
53 | }
54 | };
55 |
56 | clickSubmit = event => {
57 | event.preventDefault();
58 | this.setState({ loading: true });
59 | const { email, password } = this.state;
60 | const user = {
61 | email,
62 | password
63 | };
64 | // console.log(user);
65 | if (this.state.recaptcha) {
66 | signin(user).then(data => {
67 | if (data.error) {
68 | this.setState({ error: data.error, loading: false });
69 | } else {
70 | // authenticate
71 | authenticate(data, () => {
72 | this.setState({ redirectToReferer: true });
73 | });
74 | }
75 | });
76 | } else {
77 | this.setState({
78 | loading: false,
79 | error: "What day is today? Please write a correct answer!"
80 | });
81 | }
82 | };
83 |
84 | signinForm = (email, password, recaptcha) => (
85 |
124 | );
125 |
126 | render() {
127 | const {
128 | email,
129 | password,
130 | error,
131 | redirectToReferer,
132 | loading,
133 | recaptcha
134 | } = this.state;
135 |
136 | if (redirectToReferer) {
137 | return ;
138 | }
139 |
140 | return (
141 |
142 |
SignIn
143 |
144 |
145 |
146 |
147 |
148 |
149 |
153 | {error}
154 |
155 |
156 | {loading ? (
157 |
158 |
Loading...
159 |
160 | ) : (
161 | ""
162 | )}
163 |
164 | {this.signinForm(email, password, recaptcha)}
165 |
166 |
167 |
171 | {" "}
172 | Forgot Password
173 |
174 |
175 |
176 | );
177 | }
178 | }
179 |
180 | export default Signin;
181 |
--------------------------------------------------------------------------------
/src/user/Signup.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { signup } from "../auth";
3 | import { Link } from "react-router-dom";
4 | import SocialLogin from "./SocialLogin";
5 |
6 | class Signup extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | name: "",
11 | email: "",
12 | password: "",
13 | error: "",
14 | open: false,
15 | recaptcha: false
16 | };
17 | }
18 |
19 | handleChange = name => event => {
20 | this.setState({ error: "" });
21 | this.setState({ [name]: event.target.value });
22 | };
23 |
24 | recaptchaHandler = e => {
25 | this.setState({ error: "" });
26 | let userDay = e.target.value.toLowerCase();
27 | let dayCount;
28 |
29 | if (userDay === "sunday") {
30 | dayCount = 0;
31 | } else if (userDay === "monday") {
32 | dayCount = 1;
33 | } else if (userDay === "tuesday") {
34 | dayCount = 2;
35 | } else if (userDay === "wednesday") {
36 | dayCount = 3;
37 | } else if (userDay === "thursday") {
38 | dayCount = 4;
39 | } else if (userDay === "friday") {
40 | dayCount = 5;
41 | } else if (userDay === "saturday") {
42 | dayCount = 6;
43 | }
44 |
45 | if (dayCount === new Date().getDay()) {
46 | this.setState({ recaptcha: true });
47 | return true;
48 | } else {
49 | this.setState({
50 | recaptcha: false
51 | });
52 | return false;
53 | }
54 | };
55 |
56 | clickSubmit = event => {
57 | event.preventDefault();
58 | const { name, email, password } = this.state;
59 | const user = {
60 | name,
61 | email,
62 | password
63 | };
64 | // console.log(user);
65 | if (this.state.recaptcha) {
66 | signup(user).then(data => {
67 | if (data.error) this.setState({ error: data.error });
68 | else
69 | this.setState({
70 | error: "",
71 | name: "",
72 | email: "",
73 | password: "",
74 | open: true
75 | });
76 | });
77 | } else {
78 | this.setState({
79 | error: "What day is today? Please write a correct answer!"
80 | });
81 | }
82 | };
83 |
84 | signupForm = (name, email, password, recaptcha) => (
85 |
133 | );
134 |
135 | render() {
136 | const { name, email, password, error, open, recaptcha } = this.state;
137 | return (
138 |
139 |
Signup
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
151 | {error}
152 |
153 |
154 |
158 | New account is successfully created. Please{" "}
159 | Sign In.
160 |
161 |
162 | {this.signupForm(name, email, password, recaptcha)}
163 |
164 | );
165 | }
166 | }
167 |
168 | export default Signup;
169 |
--------------------------------------------------------------------------------
/src/user/SocialLogin.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import GoogleLogin from 'react-google-login';
4 | import { socialLogin, authenticate } from '../auth';
5 |
6 | class SocialLogin extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | redirectToReferrer: false
11 | };
12 | }
13 |
14 | responseGoogle = response => {
15 | // console.log('response', response);
16 | const tokenId = response.tokenId;
17 | const user = {
18 | tokenId: tokenId
19 | };
20 |
21 | socialLogin(user).then(data => {
22 | // console.log('signin data: ', data);
23 | if (data.error) {
24 | console.log('Error Login. Please try again..');
25 | } else {
26 | // console.log('signin success - setting jwt: ', data);
27 | authenticate(data, () => {
28 | console.log('social login response from api', data);
29 | this.setState({ redirectToReferrer: true });
30 | });
31 | }
32 | });
33 | };
34 |
35 | render() {
36 | // redirect
37 | const { redirectToReferrer } = this.state;
38 | if (redirectToReferrer) {
39 | return ;
40 | }
41 |
42 | return (
43 |
49 | );
50 | }
51 | }
52 |
53 | export default SocialLogin;
54 |
--------------------------------------------------------------------------------
/src/user/Users.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { list } from "./apiUser";
3 | import DefaultProfile from "../images/avatar.jpg";
4 | import { Link } from "react-router-dom";
5 |
6 | class Users extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | users: []
11 | };
12 | }
13 |
14 | componentDidMount() {
15 | list().then(data => {
16 | if (data.error) {
17 | console.log(data.error);
18 | } else {
19 | this.setState({ users: data });
20 | }
21 | });
22 | }
23 |
24 | renderUsers = users => (
25 |
26 | {users.map((user, i) => (
27 |
28 |

(i.target.src = `${DefaultProfile}`)}
35 | alt={user.name}
36 | />
37 |
38 |
{user.name}
39 |
{user.email}
40 |
44 | View Profile
45 |
46 |
47 |
48 | ))}
49 |
50 | );
51 |
52 | render() {
53 | const { users } = this.state;
54 | return (
55 |
56 |
Users
57 |
58 | {this.renderUsers(users)}
59 |
60 | );
61 | }
62 | }
63 |
64 | export default Users;
65 |
--------------------------------------------------------------------------------
/src/user/apiUser.js:
--------------------------------------------------------------------------------
1 | export const read = (userId, token) => {
2 | return fetch(`${process.env.REACT_APP_API_URL}/user/${userId}`, {
3 | method: "GET",
4 | headers: {
5 | Accept: "application/json",
6 | "Content-Type": "application/json",
7 | Authorization: `Bearer ${token}`
8 | }
9 | })
10 | .then(response => {
11 | return response.json();
12 | })
13 | .catch(err => console.log(err));
14 | };
15 |
16 | export const update = (userId, token, user) => {
17 | console.log("USER DATA UPDATE: ", user);
18 | return fetch(`${process.env.REACT_APP_API_URL}/user/${userId}`, {
19 | method: "PUT",
20 | headers: {
21 | Accept: "application/json",
22 | Authorization: `Bearer ${token}`
23 | },
24 | body: user
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(err => console.log(err));
30 | };
31 |
32 | export const remove = (userId, token) => {
33 | return fetch(`${process.env.REACT_APP_API_URL}/user/${userId}`, {
34 | method: "DELETE",
35 | headers: {
36 | Accept: "application/json",
37 | "Content-Type": "application/json",
38 | Authorization: `Bearer ${token}`
39 | }
40 | })
41 | .then(response => {
42 | return response.json();
43 | })
44 | .catch(err => console.log(err));
45 | };
46 |
47 | export const list = () => {
48 | return fetch(`${process.env.REACT_APP_API_URL}/users`, {
49 | method: "GET"
50 | })
51 | .then(response => {
52 | return response.json();
53 | })
54 | .catch(err => console.log(err));
55 | };
56 |
57 | export const updateUser = (user, next) => {
58 | if (typeof window !== "undefined") {
59 | if (localStorage.getItem("jwt")) {
60 | let auth = JSON.parse(localStorage.getItem("jwt"));
61 | auth.user = user;
62 | localStorage.setItem("jwt", JSON.stringify(auth));
63 | next();
64 | }
65 | }
66 | };
67 |
68 | export const follow = (userId, token, followId) => {
69 | return fetch(`${process.env.REACT_APP_API_URL}/user/follow`, {
70 | method: "PUT",
71 | headers: {
72 | Accept: "application/json",
73 | "Content-Type": "application/json",
74 | Authorization: `Bearer ${token}`
75 | },
76 | body: JSON.stringify({ userId, followId })
77 | })
78 | .then(response => {
79 | return response.json();
80 | })
81 | .catch(err => console.log(err));
82 | };
83 |
84 | export const unfollow = (userId, token, unfollowId) => {
85 | return fetch(`${process.env.REACT_APP_API_URL}/user/unfollow`, {
86 | method: "PUT",
87 | headers: {
88 | Accept: "application/json",
89 | "Content-Type": "application/json",
90 | Authorization: `Bearer ${token}`
91 | },
92 | body: JSON.stringify({ userId, unfollowId })
93 | })
94 | .then(response => {
95 | return response.json();
96 | })
97 | .catch(err => console.log(err));
98 | };
99 |
100 | export const findPeople = (userId, token) => {
101 | return fetch(`${process.env.REACT_APP_API_URL}/user/findpeople/${userId}`, {
102 | method: "GET",
103 | headers: {
104 | Accept: "application/json",
105 | "Content-Type": "application/json",
106 | Authorization: `Bearer ${token}`
107 | }
108 | })
109 | .then(response => {
110 | return response.json();
111 | })
112 | .catch(err => console.log(err));
113 | };
114 |
--------------------------------------------------------------------------------