├── client
├── .env.example
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.js
│ ├── components
│ ├── misc
│ │ ├── ErrorPage.jsx
│ │ ├── ForceRedirect.jsx
│ │ ├── LoadingScreen.jsx
│ │ ├── LocalizeEnglish.jsx
│ │ ├── LogoutPrompt.jsx
│ │ └── ProfileRedirect.jsx
│ ├── profiles
│ │ ├── FriendList.jsx
│ │ ├── Profile.jsx
│ │ └── UserSettings.jsx
│ ├── signin
│ │ ├── Homepage.jsx
│ │ ├── LoginForm.jsx
│ │ ├── RegisterForm.jsx
│ │ └── utils
│ │ │ └── validateRegister.js
│ └── user
│ │ ├── Comment.jsx
│ │ ├── CommentForm.jsx
│ │ ├── CommentList.jsx
│ │ ├── Dashboard.jsx
│ │ ├── FriendRequests.jsx
│ │ ├── FullPost.jsx
│ │ ├── Navbar.jsx
│ │ ├── NavbarOptions.jsx
│ │ ├── Post.jsx
│ │ ├── PostBox.jsx
│ │ ├── PostBoxContainer.jsx
│ │ ├── PostList.jsx
│ │ ├── SearchBar.jsx
│ │ └── SearchResults.jsx
│ ├── images
│ ├── favicon.svg
│ ├── logo.svg
│ └── no-avatar.png
│ ├── index.css
│ ├── index.js
│ └── localization.js
└── server
├── .env.example
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
└── src
├── app.js
├── config
└── passport.config.js
├── controllers
├── auth.js
├── commentController.js
├── friendRequestController.js
├── postController.js
└── userController.js
├── models
├── Comment.js
├── FriendRequest.js
├── Post.js
└── User.js
├── routes
└── api.js
└── tests
├── comments.test.js
├── friendrequests.test.js
├── jest.config.js
├── mongo.config.js
├── posts.test.js
└── users.test.js
/client/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL='theURLofYourApi'
2 | REACT_APP_MAIN_URL='theURLofThisReactApp'
--------------------------------------------------------------------------------
/client/.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 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
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 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Project: Facebook clone || Front-End
2 | Made for The Odin Project.
3 |
4 | ## About
5 | This project was made for a course of Node.js from The Odin Project. By working on this project, I could:
6 | * Handle a diverse amount of problems and situations
7 | * Secure a rest-ish API against CSRF using JWT
8 | * Learn more about acessibility
9 | * Work with React hooks such as useState and useEffect
10 |
11 | ## Getting Started
12 | Follow these instructions to set up the project locally.
13 |
14 | ### Prerequisites
15 | * npm
16 | * Node.js
17 |
18 | ### Installation
19 | 1. Get a free Mongo database at [the official website](https://www.mongodb.com/cloud/atlas)
20 | 2. Clone the repo
21 | 3. Install dependencies with NPM
22 |
23 | ```
24 | npm install
25 | ```
26 |
27 | 4. Set the environment variables necessary for this project to work. All of them are specified on the .env.example file.
28 |
29 | 5. Run `npm start` to start the React server.
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-free": "^5.15.1",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.5.0",
9 | "@testing-library/user-event": "^7.2.1",
10 | "axios": "^0.21.0",
11 | "fake-email": "0.0.3",
12 | "js-cookie": "^2.2.1",
13 | "react": "^17.0.0",
14 | "react-dom": "^17.0.0",
15 | "react-router-dom": "^5.2.0",
16 | "react-router-redirect": "^1.0.1",
17 | "react-scripts": "3.4.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": "react-app"
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | },
40 | "devDependencies": {
41 | "gh-pages": "^3.1.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucaskenji/odinclone-app/2868d6dd7f207e5f03ecc86db58503e8ad50c432/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucaskenji/odinclone-app/2868d6dd7f207e5f03ecc86db58503e8ad50c432/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucaskenji/odinclone-app/2868d6dd7f207e5f03ecc86db58503e8ad50c432/client/public/logo512.png
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './index.css';
3 | import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
4 | import '@fortawesome/fontawesome-free/css/solid.min.css';
5 | import '@fortawesome/fontawesome-free/css/regular.min.css';
6 | import axios from 'axios';
7 | import {
8 | BrowserRouter as Router,
9 | Route,
10 | Switch
11 | } from 'react-router-dom';
12 |
13 | import RegisterForm from './components/signin/RegisterForm';
14 | import Profile from './components/profiles/Profile';
15 | import FriendList from './components/profiles/FriendList';
16 | import FriendRequests from './components/user/FriendRequests';
17 | import LogoutPrompt from './components/misc/LogoutPrompt';
18 | import Dashboard from './components/user/Dashboard';
19 | import FullPost from './components/user/FullPost';
20 | import SearchResults from './components/user/SearchResults';
21 | import Navbar from './components/user/Navbar';
22 | import ErrorPage from './components/misc/ErrorPage';
23 | import ForceRedirect from './components/misc/ForceRedirect';
24 | import ProfileRedirect from './components/misc/ProfileRedirect';
25 | import UserSettings from './components/profiles/UserSettings';
26 | import LoadingScreen from './components/misc/LoadingScreen';
27 | import LocalizeEnglish from './components/misc/LocalizeEnglish';
28 |
29 | class App extends React.Component {
30 | state = {
31 | loading: true,
32 | isLogged: false,
33 | loggedUserId: ''
34 | }
35 |
36 | verifyAuth = () => {
37 | return new Promise((resolve, reject) => {
38 | axios.get(`${process.env.REACT_APP_API_URL}/api/islogged`, { withCredentials: true })
39 | .then((response) => {
40 | this.setState({
41 | isLogged: response.data.isLogged,
42 | loading: false,
43 | loggedUserId: response.data.isLogged ? response.data.id : ''
44 | });
45 |
46 | resolve();
47 | })
48 | .catch((err) => {
49 | this.setState({
50 | isLogged: false,
51 | loading: false,
52 | loggedUserId: ''
53 | });
54 | reject();
55 | });
56 | });
57 | }
58 |
59 | finishLoading = () => {
60 | this.setState({ loading: false });
61 | }
62 |
63 | render() {
64 | return (
65 |
66 |
67 |
68 | {this.state.isLogged && }
69 |
70 |
71 |
72 |
73 | }
77 | />
78 | }
82 | />
83 | }
87 | />
88 | }
92 | />
93 | }
97 | />
98 | }
102 | />
103 | }
107 | />
108 | }
112 | />
113 | }
117 | />
118 | }
122 | />
123 | }
127 | />
128 | }
132 | />
133 | }
140 | />
141 |
142 |
143 |
144 | );
145 | }
146 | }
147 |
148 |
149 | export default App;
150 |
--------------------------------------------------------------------------------
/client/src/components/misc/ErrorPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | function ErrorPage(props) {
4 | const { finishLoading } = props;
5 |
6 | useEffect(() => {
7 | finishLoading();
8 | }, [finishLoading])
9 |
10 | return (
11 |
12 |
{ props.errorTitle }
13 | {props.errorMessage}
14 |
15 | );
16 | }
17 |
18 | export default ErrorPage;
--------------------------------------------------------------------------------
/client/src/components/misc/ForceRedirect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import Cookies from 'js-cookie';
4 |
5 | function ForceRedirect(props) {
6 | // Previously used to set user id and csrf token on localStorage, and now only CSRF token.
7 |
8 | localStorage.setItem('csrfToken', Cookies.get('CSRF'));
9 | return ();
10 | }
11 |
12 | export default ForceRedirect;
--------------------------------------------------------------------------------
/client/src/components/misc/LoadingScreen.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import localStrings from '../../localization';
3 |
4 | function LoadingScreen(props) {
5 | const { loading } = props.state;
6 | const isLoadingHomepage = window.location.href === process.env.REACT_APP_MAIN_URL;
7 | const locale = localStorage.getItem('localizationCode') === 'en-US' ? 'en-US' : 'pt-BR';
8 |
9 | return (
10 |
11 |
12 |
13 |
14 | { isLoadingHomepage ? localStrings[locale]['loading']['mainPage'] : localStrings[locale]['loading']['normal'] }
15 |
16 | );
17 | }
18 |
19 | export default LoadingScreen;
--------------------------------------------------------------------------------
/client/src/components/misc/LocalizeEnglish.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | function LocalizeEnglish(props) {
5 | localStorage.setItem('localizationCode', 'en-US');
6 |
7 | return ();
8 | }
9 |
10 | export default LocalizeEnglish;
--------------------------------------------------------------------------------
/client/src/components/misc/LogoutPrompt.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import axios from 'axios';
4 |
5 | function LogoutPrompt(props) {
6 | const [loggedOut, setLoggedOut] = useState(false);
7 | const [updatedState, setUpdatedState] = useState(false);
8 | const { verifyAuth } = props;
9 |
10 | useEffect(() => {
11 | if (!loggedOut) {
12 | axios.get(`${process.env.REACT_APP_API_URL}/api/logout`, { withCredentials: true })
13 | .then((response) => {
14 | setLoggedOut(true);
15 | })
16 | .catch((err) => {
17 | // It's hard for this route to fail, but a connection error would probably logout the user anyway
18 | setLoggedOut(true);
19 | })
20 | } else {
21 | verifyAuth()
22 | .then(() => {
23 | setUpdatedState(true);
24 | })
25 | .catch((err) => {
26 | // It's hard for verifyAuth' route to fail, but a connection error would probably logout the user anyway
27 | setUpdatedState(true);
28 | })
29 | }
30 | }, [loggedOut, verifyAuth]);
31 |
32 | if (loggedOut && updatedState) {
33 | return
34 | } else {
35 | return 'Logging out...';
36 | }
37 | }
38 |
39 | export default LogoutPrompt;
--------------------------------------------------------------------------------
/client/src/components/misc/ProfileRedirect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useParams, Redirect } from 'react-router-dom';
3 |
4 | function ProfileRedirect(props) {
5 | const { profileId } = useParams();
6 | return ()
7 | };
8 |
9 | export default ProfileRedirect;
--------------------------------------------------------------------------------
/client/src/components/profiles/FriendList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useParams, Redirect, Link } from 'react-router-dom';
3 | import axios from 'axios';
4 | import noAvatar from '../../images/no-avatar.png';
5 | import localStrings from '../../localization';
6 |
7 | function FriendList(props) {
8 | const { verifyAuth } = props;
9 | const { userId } = useParams();
10 | const [friends, setFriends] = useState([]);
11 | const [displayedFriends, setDisplayedFriends] = useState([]);
12 | const [isUnmounted, setIsUnmounted] = useState(false);
13 | const locale = localStorage.getItem('localizationCode') === 'en-US' ? 'en-US' : 'pt-BR';
14 |
15 | useEffect(() => {
16 | return () => {setIsUnmounted(true)};
17 | }, [verifyAuth, userId])
18 |
19 | useEffect(() => {
20 | verifyAuth();
21 | }, [verifyAuth]);
22 |
23 | useEffect(() => {
24 | axios.get(`${process.env.REACT_APP_API_URL}/api/users/${userId}`)
25 | .then((response) => {
26 | if (!isUnmounted) {
27 | setFriends(response.data.friends);
28 | setDisplayedFriends(response.data.friends);
29 | }
30 | })
31 | .catch((err) => {
32 | // Routes are tested and a connection error would redirect the user
33 | })
34 | }, [userId, isUnmounted]);
35 |
36 | const handleSearch = (ev) => {
37 | const query = new RegExp(ev.target.value, 'i');
38 | const results = [...friends].filter((f) => query.test(f.firstName) || query.test(f.lastName));
39 |
40 | setDisplayedFriends(results);
41 | }
42 |
43 | if (props.state.loading) {
44 | return 'Loading...';
45 | } else if (!props.state.isLogged) {
46 | return ();
47 | } else {
48 | return (
49 |
50 |
51 |
69 |
70 |
71 | {
72 | displayedFriends.length === 0 && localStrings[locale]['friendList']['noFriends']
73 | }
74 |
75 | {
76 | displayedFriends.map((friend) =>
77 |
78 |
79 |
![{localStrings[locale]['friendList']['alt']['avatar']}]({friend.photo)
80 |
81 |
82 | {friend.firstName} {friend.lastName}
83 |
84 |
)
85 | }
86 |
87 |
88 | );
89 | }
90 | }
91 |
92 | export default FriendList;
--------------------------------------------------------------------------------
/client/src/components/profiles/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useParams, Redirect } from 'react-router-dom';
3 | import axios from 'axios';
4 | import PostList from '../user/PostList';
5 | import noAvatar from '../../images/no-avatar.png';
6 | import ErrorPage from '../misc/ErrorPage';
7 | import localStrings from '../../localization';
8 |
9 | function Profile(props) {
10 | const [user, setUser] = useState(null);
11 | const [isSameUser, setIsSameUser] = useState(false);
12 | const [isFriend, setIsFriend] = useState(false);
13 | const [friendRequestId, setFriendRequestId] = useState('');
14 | const [finishedAsync, setFinishedAsync] = useState(true);
15 | const [requestedUser, setRequestedUser] = useState(false);
16 | const [errorMessage, setErrorMessage] = useState('');
17 | const [fatalError, setFatalError] = useState(false);
18 | const [isUnmounted, setIsUnmounted] = useState(false);
19 | const { userId } = useParams();
20 | const { verifyAuth } = props;
21 | const { loggedUserId } = props.state;
22 | const locale = localStorage.getItem('localizationCode') === 'en-US' ? 'en-US' : 'pt-BR';
23 |
24 | useEffect(() => {
25 | return () => { setIsUnmounted(true) }
26 | }, []);
27 |
28 | useEffect(() => {
29 | verifyAuth();
30 | }, [verifyAuth]);
31 |
32 | useEffect(() => {
33 | if (!loggedUserId) {
34 | return;
35 | }
36 |
37 | axios.get(`${process.env.REACT_APP_API_URL}/api/users/${userId}`)
38 | .then((response) => {
39 | if (!isUnmounted) {
40 | setUser({
41 | firstName: response.data.firstName,
42 | lastName: response.data.lastName,
43 | birthDate: new Date(response.data.birthDate),
44 | gender: response.data.gender,
45 | friends: response.data.friends,
46 | photo: response.data.photo
47 | });
48 | }
49 |
50 | const friendIds = [...response.data.friends].map((friend) => friend._id);
51 |
52 | if (friendIds.indexOf(loggedUserId) !== -1 && !isUnmounted) {
53 | setIsFriend(true);
54 | }
55 | })
56 | .catch((err) => {
57 | setFatalError(true);
58 | })
59 | }, [userId, isUnmounted, loggedUserId]);
60 |
61 | useEffect(() => {
62 | if (!loggedUserId) {
63 | return;
64 | }
65 |
66 | axios.get(`${process.env.REACT_APP_API_URL}/api/users/${userId}/friendrequests`)
67 | .then((response) => {
68 | const requestFromUser = response.data.find((request) => request.sender._id === loggedUserId);
69 |
70 | if (!isUnmounted) {
71 | if (requestFromUser == null) {
72 | setFriendRequestId('');
73 | } else {
74 | setFriendRequestId(requestFromUser._id);
75 | }
76 | }
77 | })
78 | .catch((err) => {
79 | if (err.response) {
80 | if (err.response.status === 404) {
81 | return;
82 | }
83 | }
84 |
85 | if (!isUnmounted) {
86 | setFatalError(true);
87 | }
88 | })
89 | }, [userId, isUnmounted, loggedUserId]);
90 |
91 | useEffect(() => {
92 | if (!loggedUserId) {
93 | return;
94 | }
95 |
96 | axios.get(`${process.env.REACT_APP_API_URL}/api/users/${loggedUserId}/friendrequests`)
97 | .then((response) => {
98 | const requestFromUser = response.data.find((request) => request.sender._id === userId);
99 |
100 | if (!isUnmounted) {
101 | if (requestFromUser == null) {
102 | setRequestedUser(false);
103 | } else {
104 | setRequestedUser(true);
105 | }
106 | }
107 | })
108 | .catch((err) => {
109 | if (err.response) {
110 | if (err.response.status === 404) {
111 | return;
112 | }
113 | }
114 |
115 | if (!isUnmounted) {
116 | setFatalError(true);
117 | }
118 | })
119 | }, [userId, isUnmounted, loggedUserId]);
120 |
121 | useEffect(() => {
122 | if (loggedUserId) {
123 | if (loggedUserId === userId && !isUnmounted) {
124 | setIsSameUser(true);
125 | }
126 | }
127 | }, [loggedUserId, userId, isUnmounted]);
128 |
129 | const handleSendRequest = () => {
130 | setFinishedAsync(false);
131 | setErrorMessage('');
132 |
133 | const newRequest = {
134 | sender: loggedUserId,
135 | receiver: userId
136 | };
137 |
138 | const csrfToken = localStorage.getItem('csrfToken');
139 |
140 | axios.post(`${process.env.REACT_APP_API_URL}/api/friendrequests`, newRequest, { withCredentials: true, headers: {csrf: csrfToken} })
141 | .then((response) => {
142 | if (!isUnmounted) {
143 | setFriendRequestId(response.data._id);
144 | setFinishedAsync(true);
145 | }
146 | })
147 | .catch((err) => {
148 | if (!isUnmounted) {
149 | setErrorMessage(localStrings[locale]['profile']['error']['internal']);
150 | }
151 | })
152 | }
153 |
154 | const handleCancelRequest = () => {
155 | setFinishedAsync(false);
156 | setErrorMessage('');
157 |
158 | axios.delete(`${process.env.REACT_APP_API_URL}/api/friendrequests/${friendRequestId}`)
159 | .then((response) => {
160 | if (!isUnmounted) {
161 | setFriendRequestId('');
162 | setFinishedAsync(true);
163 | }
164 | })
165 | .catch((err) => {
166 | if (!isUnmounted) {
167 | setErrorMessage(localStrings[locale]['profile']['error']['internal']);
168 | }
169 | })
170 | }
171 |
172 | const handleUnfriend = async () => {
173 | setFinishedAsync(false);
174 | setErrorMessage('');
175 |
176 | try {
177 | await axios.put(`${process.env.REACT_APP_API_URL}/api/users/${loggedUserId}/unfriend`, { _id: userId });
178 | await axios.put(`${process.env.REACT_APP_API_URL}/api/users/${userId}/unfriend`, { _id: loggedUserId });
179 |
180 | const newUser = {...user};
181 | newUser.friends = newUser.friends.filter((friend) => friend._id.toString() !== loggedUserId);
182 |
183 | if (!isUnmounted) {
184 | setIsFriend(false);
185 | setUser(newUser);
186 | setFinishedAsync(true);
187 | }
188 | } catch (err) {
189 | if (!isUnmounted) {
190 | setErrorMessage(localStrings[locale]['profile']['error']['internal']);
191 | }
192 | }
193 | }
194 |
195 | const acceptRequest = () => {
196 | window.location.href = '/requests';
197 | }
198 |
199 | if (fatalError) {
200 | return (
201 |
205 | );
206 | }
207 |
208 | if (props.state.loading) {
209 | return 'Loading auth...';
210 | } else if (!props.state.isLogged) {
211 | return ();
212 | } else if (user) {
213 | return (
214 |
215 |
216 |
217 |
![{localStrings[locale]['profile']['alt']['avatar']}]({user.photo)
218 |
219 |
220 |
{user.firstName} {user.lastName}
221 | {localStrings[locale]['profile']['bornPrefix']} {user.birthDate.toLocaleDateString(locale)},
222 | {
223 | user.gender === 'undefined'
224 | ? localStrings[locale]['profile']['noGender']
225 | : user.gender === 'male'
226 | ? localStrings[locale]['profile']['male']
227 | : user.gender === 'female'
228 | ? localStrings[locale]['profile']['female']
229 | : localStrings[locale]['profile']['other']
230 | }
231 |
232 |
233 |
234 | {
235 | isSameUser
236 | ? ''
237 | : ( isFriend
238 | ?
241 | : ( requestedUser
242 | ?
245 | : ( friendRequestId
246 | ?
249 | :
252 | )
253 | )
254 | )
255 | }
256 |
257 | { errorMessage && {errorMessage} }
258 |
259 |
260 |
261 |
262 |
282 |
283 |
284 |
285 |
286 |
287 | );
288 | } else {
289 | return 'Loading user...';
290 | }
291 | }
292 |
293 | export default Profile;
--------------------------------------------------------------------------------
/client/src/components/profiles/UserSettings.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import axios from 'axios';
3 | import { Redirect } from 'react-router-dom';
4 | import { validateName, validateEmail, validatePassword } from '../signin/utils/validateRegister';
5 | import localStrings from '../../localization';
6 |
7 | function UserSettings(props) {
8 | const { verifyAuth } = props;
9 | const { loggedUserId } = props.state;
10 | const [finishedAsync, setFinishedAsync] = useState(true);
11 | const [user, setUser] = useState({});
12 | const [photoUrl, setPhotoUrl] = useState('');
13 | const [urlIsValid, setUrlIsValid] = useState(true);
14 | const [validatedUrl, setValidatedUrl] = useState(true);
15 | const [errorMessage, setErrorMessage] = useState('');
16 | const locale = localStorage.getItem('localizationCode') === 'en-US' ? 'en-US' : 'pt-BR';
17 |
18 | useEffect(() => {
19 | verifyAuth();
20 | }, [ verifyAuth ]);
21 |
22 | useEffect(() => {
23 | if (loggedUserId) {
24 | axios.get(`${process.env.REACT_APP_API_URL}/api/users/${loggedUserId}`)
25 | .then((response) => {
26 | const userWithStringData = {...response.data};
27 | userWithStringData.birthDate = new Date(response.data.birthDate);
28 | setUser(userWithStringData);
29 | })
30 | .catch((err) => {
31 | // Routes are tested and a connection error would redirect the user anyway
32 | })
33 | }
34 | }, [loggedUserId])
35 |
36 |
37 | const requestUpdate = (form) => {
38 | form.preventDefault();
39 | setFinishedAsync(false);
40 | setErrorMessage('');
41 |
42 | // Checks validation
43 | if (!validatedUrl || !urlIsValid) {
44 | setFinishedAsync(true);
45 | return;
46 | }
47 |
48 | const fields = form.target;
49 | const birthDate = new Date(fields.birthYear.value, fields.birthMonth.value, fields.birthDay.value);
50 |
51 | if (validateName(fields.firstName).valid === false || validateName(fields.lastName).valid === false) {
52 | setFinishedAsync(true);
53 | return;
54 | }
55 |
56 | if (!user.facebookId) {
57 | if (validateEmail(fields.email).valid === false || validatePassword(fields.password).valid === false) {
58 | setFinishedAsync(true);
59 | return;
60 | }
61 | }
62 |
63 | // Updates
64 | const updatedUser = {
65 | firstName: fields.firstName.value,
66 | lastName: fields.lastName.value,
67 | photo: fields.photoUrl.value,
68 | birthDate,
69 | gender: fields.gender.value
70 | };
71 |
72 | if (!user.facebookId) {
73 | updatedUser.email = fields.email.value;
74 | updatedUser.password = fields.password.value;
75 | }
76 |
77 | axios.put(`${process.env.REACT_APP_API_URL}/api/users/${loggedUserId}`, updatedUser)
78 | .then((response) => {
79 | setFinishedAsync(true);
80 | window.location.href='/profile/' + loggedUserId;
81 | })
82 | .catch((err) => {
83 | if (err.response) {
84 | if (err.response.status === 409) {
85 | setErrorMessage(localStrings[locale]['settings']['error']['emailConflict']);
86 | } else {
87 | setErrorMessage(localStrings[locale]['settings']['error']['invalidData']);
88 | }
89 | } else {
90 | setErrorMessage(localStrings[locale]['settings']['error']['internal']);
91 | }
92 | setFinishedAsync(true);
93 | })
94 | }
95 |
96 | const handleInputUrl = (input) => {
97 | setValidatedUrl(false);
98 | setPhotoUrl(input.target.value);
99 | }
100 |
101 | const handleLoadImage = () => {
102 | setUrlIsValid(true);
103 | setValidatedUrl(true);
104 | }
105 |
106 | const handleErrorImage = () => {
107 | setValidatedUrl(true);
108 |
109 | if (photoUrl.length === 0) {
110 | setUrlIsValid(true);
111 | return;
112 | }
113 |
114 | setUrlIsValid(false);
115 | }
116 |
117 | // All years available on the birth year