Login Logout Navbar ProfileForm moved to MUI

This commit is contained in:
QkoSad
2023-08-14 19:10:42 +03:00
parent e86db26687
commit 7cd63ec826
50 changed files with 3356 additions and 5054 deletions
+2074 -4050
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -3,6 +3,10 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.4",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

+4 -39
View File
@@ -1,49 +1,14 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link <link
rel="stylesheet" rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
crossorigin="anonymous"
/> />
<meta <meta charset="utf-8" />
name="description" <meta name="viewport" content="width=device-width, initial-scale=1" />
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body> </body>
</html> </html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

-25
View File
@@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
-3
View File
@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+16 -18
View File
@@ -1,4 +1,3 @@
/* Global Styles */ /* Global Styles */
:root { :root {
--primary-color: #17a2b8; --primary-color: #17a2b8;
@@ -15,7 +14,7 @@
} }
body { body {
font-family: 'Raleway', sans-serif; font-family: "Raleway", sans-serif;
font-size: 1rem; font-size: 1rem;
line-height: 1.6; line-height: 1.6;
background-color: #fff; background-color: #fff;
@@ -246,10 +245,10 @@ img {
color: #888; color: #888;
} }
.form input[type='text'], .form input[type="text"],
.form input[type='email'], .form input[type="email"],
.form input[type='password'], .form input[type="password"],
.form input[type='date'], .form input[type="date"],
.form select, .form select,
.form textarea { .form textarea {
display: block; display: block;
@@ -259,7 +258,7 @@ img {
border: 1px solid #ccc; border: 1px solid #ccc;
} }
.form input[type='submit'], .form input[type="submit"],
button { button {
font: inherit; font: inherit;
} }
@@ -334,7 +333,7 @@ button {
/* Landing Page */ /* Landing Page */
.landing { .landing {
position: relative; position: relative;
background: url('./img/vimCheatSheet.jpg') no-repeat center center/cover; background: url("./img/vimCheatSheet.jpg") no-repeat center center/cover;
height: 100vh; height: 100vh;
} }
@@ -365,10 +364,10 @@ button {
.profile-grid { .profile-grid {
display: grid; display: grid;
grid-template-areas: grid-template-areas:
'top top' "top top"
'about about' "about about"
'exp edu' "exp edu"
'github github'; "github github";
grid-gap: 1rem; grid-gap: 1rem;
} }
@@ -551,11 +550,11 @@ button {
.profile-grid { .profile-grid {
grid-template-areas: grid-template-areas:
'top' "top"
'about' "about"
'exp' "exp"
'edu' "edu"
'github'; "github";
} }
.profile-about .skills { .profile-about .skills {
@@ -583,4 +582,3 @@ button {
right: 2rem; right: 2rem;
display: inline-block; display: inline-block;
} }
+22 -23
View File
@@ -1,30 +1,29 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Navbar from './components/layout/Navbar'; import Navbar from "./components/layout/Navbar";
import Landing from './components/layout/Landing'; import Landing from "./components/layout/Landing";
import Register from './components/auth/Register'; import Register from "./components/auth/Register";
import Login from './components/auth/Login'; import Login from "./components/auth/Login";
import Alert from './components/layout/Alert'; import Alert from "./components/layout/Alert";
import Dashboard from './components/dashboard/Dashboard'; import Dashboard from "./components/dashboard/Dashboard";
import ProfileForm from './components/profile-forms/ProfileForm'; import ProfileForm from "./components/profile-forms/ProfileForm";
import AddExperience from './components/profile-forms/AddExperience'; import AddExperience from "./components/profile-forms/AddExperience";
import AddEducation from './components/profile-forms/AddEducation'; import AddEducation from "./components/profile-forms/AddEducation";
import Profiles from './components/profiles/Profiles'; import Profiles from "./components/profiles/Profiles";
import Profile from './components/profile/Profile'; import Profile from "./components/profile/Profile";
import Posts from './components/posts/Posts'; import Posts from "./components/posts/Posts";
import Post from './components/post/Post'; import Post from "./components/post/Post";
import NotFound from './components/layout/NotFound'; import NotFound from "./components/layout/NotFound";
import PrivateRoute from './components/routing/PrivateRoute'; import PrivateRoute from "./components/routing/PrivateRoute";
// Redux // Redux
import { Provider } from 'react-redux'; import { Provider } from "react-redux";
import store from './store'; import store from "./store";
import { loadUser } from './actions/auth'; import { loadUser } from "./actions/auth";
import setAuthToken from './utils/setAuthToken'; import setAuthToken from "./utils/setAuthToken";
import './App.css'; import { logOut } from "./reducers/auth";
import { logOut } from './reducers/auth';
const App = () => { const App = () => {
useEffect(() => { useEffect(() => {
+7
View File
@@ -0,0 +1,7 @@
a {
text-decoration: none;
color: darkorange;
}
body {
background-color: whitesmoke;
}
+10 -8
View File
@@ -1,10 +1,12 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from "uuid";
import { removeAlert, setAlert } from '../reducers/alert'; import { removeAlert, setAlert } from "../reducers/alert";
import { AppThunk } from '../types'; import { AppThunk } from "../types";
export const createAlert = (msg: string, alertType: string, timeout = 5000): AppThunk => dispatch => { export const createAlert =
const id = uuidv4(); (msg: string, alertType: string, timeout = 5000): AppThunk =>
dispatch(setAlert({ msg, alertType, id })); (dispatch) => {
const id = uuidv4();
dispatch(setAlert({ msg, alertType, id }));
setTimeout(() => dispatch(removeAlert(id)), timeout); setTimeout(() => dispatch(removeAlert(id)), timeout);
}; };
+49 -37
View File
@@ -10,30 +10,34 @@ import {
import { AppThunk } from "../types"; import { AppThunk } from "../types";
import { AxiosError, isAxiosError } from "axios"; import { AxiosError, isAxiosError } from "axios";
export const login = (email: string, password: string): AppThunk => async (dispatch) => { export const login =
const body = { email, password }; (email: string, password: string): AppThunk =>
try { async (dispatch) => {
const res = await api.post("/auth", body); const body = { email, password };
try {
const res = await api.post("/auth", body);
dispatch(loginSucces(res.data)); dispatch(loginSucces(res.data));
dispatch(loadUser()); dispatch(loadUser());
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError || err instanceof Error) { if (err instanceof AxiosError || err instanceof Error) {
if (isAxiosError(err) && err.response !== undefined) { if (isAxiosError(err) && err.response !== undefined) {
const errors = err.response.data.errors; const errors = err.response.data.errors;
if (errors) { if (errors) {
errors.forEach((error: any) => dispatch(createAlert(error.msg, "danger"))); errors.forEach((error: any) =>
dispatch(createAlert(error.msg, "danger")),
);
}
dispatch({
type: "auth/loginFail",
});
} }
dispatch({
type: "auth/loginFail",
});
} }
//normal err
} }
//normal err // not error
} };
// not error
}
export const loadUser = (): AppThunk => async (dispatch) => { export const loadUser = (): AppThunk => async (dispatch) => {
try { try {
@@ -46,28 +50,36 @@ export const loadUser = (): AppThunk => async (dispatch) => {
}; };
// Register User // Register User
export const register = (formData: { name: string, email: string, password: string }): AppThunk => async (dispatch) => { export const register =
try { (formData: { name: string; email: string; password: string }): AppThunk =>
const res = await api.post("/users", formData); async (dispatch) => {
try {
const res = await api.post("/users", formData);
dispatch(registerSuccess(res.data)); dispatch(registerSuccess(res.data));
dispatch(loadUser()); dispatch(loadUser());
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
const errors = err.response.data.errors; err !== undefined &&
"response" in err &&
err.response !== undefined
) {
const errors = err.response.data.errors;
if (errors) { if (errors) {
errors.forEach((error: any) => dispatch(createAlert(error.msg, "danger"))); errors.forEach((error: any) =>
dispatch(createAlert(error.msg, "danger")),
);
}
dispatch({
type: "auth/registerFail",
});
} }
dispatch({
type: "auth/registerFail",
});
} }
}; }
} };
}
export const logout = (): AppThunk => async (dispatch) => { export const logout = (): AppThunk => async (dispatch) => {
dispatch(logOut); dispatch(logOut);
+165 -97
View File
@@ -21,9 +21,16 @@ export const getPosts = (): AppThunk => async (dispatch) => {
dispatch(getPostsAction(res.data)); dispatch(getPostsAction(res.data));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
err !== undefined &&
"response" in err &&
err.response !== undefined
) {
dispatch( dispatch(
postError({ msg: err.response.statusText, status: err.response.status }) postError({
msg: err.response.statusText,
status: err.response.status,
}),
); );
} }
} }
@@ -31,127 +38,188 @@ export const getPosts = (): AppThunk => async (dispatch) => {
}; };
// Add like // Add like
export const addLike = (id: string): AppThunk => async (dispatch) => { export const addLike =
try { (id: string): AppThunk =>
const res = await api.put(`/posts/like/${id}`); async (dispatch) => {
try {
const res = await api.put(`/posts/like/${id}`);
dispatch(updateLikes({ id, likes: res.data })); dispatch(updateLikes({ id, likes: res.data }));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
} };
}
// Remove like // Remove like
export const removeLike = (id: string): AppThunk => async (dispatch) => { export const removeLike =
try { (id: string): AppThunk =>
const res = await api.put(`/posts/unlike/${id}`); async (dispatch) => {
try {
const res = await api.put(`/posts/unlike/${id}`);
dispatch(updateLikes({ id, likes: res.data })); dispatch(updateLikes({ id, likes: res.data }));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
};
}
}
// Delete post // Delete post
export const deletePost = (id: string): AppThunk => async (dispatch) => { export const deletePost =
try { (id: string): AppThunk =>
await api.delete(`/posts/${id}`); async (dispatch) => {
try {
await api.delete(`/posts/${id}`);
dispatch(deletePostAction(id)); dispatch(deletePostAction(id));
dispatch(createAlert("Post Removed", "success")); dispatch(createAlert("Post Removed", "success"));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
};
}
}
// Add post // Add post
export const addPost = (formData: { text: string }): AppThunk => async (dispatch) => { export const addPost =
try { (formData: { text: string }): AppThunk =>
const res = await api.post("/posts", formData); async (dispatch) => {
try {
const res = await api.post("/posts", formData);
dispatch(addPostAction(res.data)); dispatch(addPostAction(res.data));
dispatch(createAlert("Post Created", "success")); dispatch(createAlert("Post Created", "success"));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
} }
}; };
}
// Get post // Get post
export const getPost = (id: string): AppThunk => async (dispatch) => { export const getPost =
try { (id: string): AppThunk =>
const res = await api.get(`/posts/${id}`); async (dispatch) => {
try {
const res = await api.get(`/posts/${id}`);
dispatch(getPostAction(res.data)); dispatch(getPostAction(res.data));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
} };
}
// Add comment // Add comment
export const addComment = (postId: string, formData: { text: string }): AppThunk => async (dispatch) => { export const addComment =
try { (postId: string, formData: { text: string }): AppThunk =>
const res = await api.post(`/posts/comment/${postId}`, formData); async (dispatch) => {
try {
const res = await api.post(`/posts/comment/${postId}`, formData);
dispatch(addCommentAction(res.data)); dispatch(addCommentAction(res.data));
dispatch(createAlert("Comment Added", "success")); dispatch(createAlert("Comment Added", "success"));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
} };
}
// Delete comment // Delete comment
export const deleteComment = (postId: string, commentId: string): AppThunk => async (dispatch) => { export const deleteComment =
try { (postId: string, commentId: string): AppThunk =>
await api.delete(`/posts/comment/${postId}/${commentId}`); async (dispatch) => {
try {
await api.delete(`/posts/comment/${postId}/${commentId}`);
dispatch(removeComment(commentId)); dispatch(removeComment(commentId));
dispatch(createAlert("Comment Removed", "success")); dispatch(createAlert("Comment Removed", "success"));
} catch (err: unknown) { } catch (err: unknown) {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (
dispatch( err !== undefined &&
postError({ msg: err.response.statusText, status: err.response.status }) "response" in err &&
); err.response !== undefined
) {
dispatch(
postError({
msg: err.response.statusText,
status: err.response.status,
}),
);
}
} }
}; }
} };
}
+168 -132
View File
@@ -15,22 +15,28 @@ import { AppThunk, EducationType, ExperienceType } from "../types";
const errorHandle = (dispatch: any, err: unknown) => { const errorHandle = (dispatch: any, err: unknown) => {
if (err instanceof AxiosError) { if (err instanceof AxiosError) {
if (err !== undefined && 'response' in err && err.response !== undefined) { if (err !== undefined && "response" in err && err.response !== undefined) {
dispatch(profileError({ msg: err.response.statusText, status: err.response.status, }), err) dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
}),
err,
);
} }
} }
} };
// Get current users profile // Get current users profile
export const getCurrentProfile = (): AppThunk<Promise<void>> => async (dispatch) => { export const getCurrentProfile =
try { (): AppThunk<Promise<void>> => async (dispatch) => {
const res = await api.get("/profile/me"); try {
const res = await api.get("/profile/me");
dispatch(getProfile(res.data)); dispatch(getProfile(res.data));
} catch (err: unknown) { } catch (err: unknown) {
errorHandle(dispatch, err) errorHandle(dispatch, err);
}
} };
}
// Get all profiles // Get all profiles
export const getProfiles = (): AppThunk<Promise<void>> => async (dispatch) => { export const getProfiles = (): AppThunk<Promise<void>> => async (dispatch) => {
dispatch(clearProfile()); dispatch(clearProfile());
@@ -39,173 +45,203 @@ export const getProfiles = (): AppThunk<Promise<void>> => async (dispatch) => {
dispatch(getProfilesType(res.data)); dispatch(getProfilesType(res.data));
} catch (err: unknown) { } catch (err: unknown) {
errorHandle(dispatch, err) errorHandle(dispatch, err);
} }
}; };
// Get profile by ID // Get profile by ID
export const getProfileById = (userId: string): AppThunk<Promise<void>> => async (dispatch) => { export const getProfileById =
try { (userId: string): AppThunk<Promise<void>> =>
const res = await api.get(`/profile/user/${userId}`); async (dispatch) => {
try {
const res = await api.get(`/profile/user/${userId}`);
dispatch(getProfile(res.data)); dispatch(getProfile(res.data));
} catch (err: unknown) { } catch (err: unknown) {
errorHandle(dispatch, err) errorHandle(dispatch, err);
} }
}; };
// Get Github repos // Get Github repos
export const getGithubRepos = (username: string): AppThunk<Promise<void>> => async (dispatch) => { export const getGithubRepos =
try { (username: string): AppThunk<Promise<void>> =>
const res = await api.get(`/profile/github/${username}`); async (dispatch) => {
try {
const res = await api.get(`/profile/github/${username}`);
dispatch(getRepos(res.data)); dispatch(getRepos(res.data));
} catch (err: unknown) { } catch (err: unknown) {
dispatch(noRepos()); dispatch(noRepos());
} }
}; };
// Create or update profile // Create or update profile
type FormDataType = { type FormDataType = {
company: string, company: string;
website: string, website: string;
location: string, location: string;
status: string, status: string;
skills: string, skills: string;
githubusername: string, githubusername: string;
bio: string, bio: string;
twitter: string, twitter: string;
facebook: string, facebook: string;
linkedin: string, linkedin: string;
youtube: string, youtube: string;
instagram: string, instagram: string;
} };
export const createProfile = export const createProfile =
(formData: FormDataType, edit = false): AppThunk<Promise<void>> => (formData: FormDataType, edit = false): AppThunk<Promise<void>> =>
async (dispatch) => { async (dispatch) => {
try { try {
const res = await api.post("/profile", formData); const res = await api.post("/profile", formData);
dispatch(getProfile(res.data)); dispatch(getProfile(res.data));
dispatch( dispatch(
createAlert(edit ? "Profile Updated" : "Profile Created", "success") createAlert(edit ? "Profile Updated" : "Profile Created", "success"),
); );
} catch (err: unknown) {
} catch (err: unknown) { if (err instanceof AxiosError) {
if (err instanceof AxiosError) { if (
if (err !== undefined && 'response' in err && err.response !== undefined) { err !== undefined &&
const errors = err.response.data.errors; "response" in err &&
if (errors) { err.response !== undefined
errors.forEach((error: any) => dispatch(createAlert(error.msg, "danger"))); ) {
} const errors = err.response.data.errors;
dispatch( if (errors) {
profileError({ errors.forEach((error: any) =>
msg: err.response.statusText, dispatch(createAlert(error.msg, "danger")),
status: err.response.status,
})
); );
} }
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
}),
);
} }
} }
}; }
};
// Add Experience // Add Experience
export const addExperience = (formData: Omit<ExperienceType, '_id'>): AppThunk<Promise<void>> => async (dispatch) => { export const addExperience =
try { (formData: Omit<ExperienceType, "_id">): AppThunk<Promise<void>> =>
const res = await api.put("/profile/experience", formData); async (dispatch) => {
try {
const res = await api.put("/profile/experience", formData);
dispatch(updateProfile(res.data)); dispatch(updateProfile(res.data));
dispatch(createAlert("Experience Added", "success")); dispatch(createAlert("Experience Added", "success"));
} catch (err: unknown) {
if (err instanceof AxiosError) {
if (
err !== undefined &&
"response" in err &&
err.response !== undefined
) {
const errors = err.response.data.errors;
} catch (err: unknown) { if (errors) {
if (err instanceof AxiosError) { errors.forEach((error: any) =>
if (err !== undefined && 'response' in err && err.response !== undefined) { dispatch(createAlert(error.msg, "danger")),
const errors = err.response.data.errors; );
}
if (errors) { dispatch(
errors.forEach((error: any) => dispatch(createAlert(error.msg, "danger"))); profileError({
msg: err.response.statusText,
status: err.response.status,
}),
);
} }
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
} }
} }
} };
};
// Add Education // Add Education
export const addEducation = (formData: Omit<EducationType, '_id'>): AppThunk<Promise<void>> => async (dispatch) => { export const addEducation =
try { (formData: Omit<EducationType, "_id">): AppThunk<Promise<void>> =>
const res = await api.put("/profile/education", formData); async (dispatch) => {
try {
const res = await api.put("/profile/education", formData);
dispatch(updateProfile(res.data)); dispatch(updateProfile(res.data));
dispatch(createAlert("Education Added", "success")); dispatch(createAlert("Education Added", "success"));
} catch (err: unknown) {
if (err instanceof AxiosError) {
if (
err !== undefined &&
"response" in err &&
err.response !== undefined
) {
const errors = err.response.data.errors;
} catch (err: unknown) { if (errors) {
if (err instanceof AxiosError) { errors.forEach((error: any) =>
if (err !== undefined && 'response' in err && err.response !== undefined) { dispatch(createAlert(error.msg, "danger")),
const errors = err.response.data.errors; );
}
if (errors) { dispatch(
errors.forEach((error: any) => dispatch(createAlert(error.msg, "danger"))); profileError({
msg: err.response.statusText,
status: err.response.status,
}),
);
} }
dispatch(
profileError({
msg: err.response.statusText,
status: err.response.status,
})
);
} }
} }
} };
};
// Delete experience // Delete experience
export const deleteExperience = (id: string): AppThunk<Promise<void>> => async (dispatch) => { export const deleteExperience =
try { (id: string): AppThunk<Promise<void>> =>
const res = await api.delete(`/profile/experience/${id}`); async (dispatch) => {
try {
const res = await api.delete(`/profile/experience/${id}`);
dispatch(updateProfile(res.data)); dispatch(updateProfile(res.data));
dispatch(createAlert("Experience Removed", "success")); dispatch(createAlert("Experience Removed", "success"));
} catch (err: unknown) { } catch (err: unknown) {
errorHandle(dispatch, err) errorHandle(dispatch, err);
} }
}; };
// Delete education // Delete education
export const deleteEducation = (id: string): AppThunk<Promise<void>> => async (dispatch) => { export const deleteEducation =
try { (id: string): AppThunk<Promise<void>> =>
const res = await api.delete(`/profile/education/${id}`); async (dispatch) => {
try {
const res = await api.delete(`/profile/education/${id}`);
dispatch(updateProfile(res.data)); dispatch(updateProfile(res.data));
dispatch(createAlert("Education Removed", "success")); dispatch(createAlert("Education Removed", "success"));
} catch (err: unknown) { } catch (err: unknown) {
errorHandle(dispatch, err) errorHandle(dispatch, err);
} }
}; };
// Delete account & profile // Delete account & profile
export const deleteAccount = (): AppThunk<Promise<void>> => async (dispatch) => { export const deleteAccount =
if (window.confirm("Are you sure? This can NOT be undone!")) { (): AppThunk<Promise<void>> => async (dispatch) => {
try { if (window.confirm("Are you sure? This can NOT be undone!")) {
await api.delete("/profile"); try {
await api.delete("/profile");
dispatch(clearProfile()); dispatch(clearProfile());
dispatch(accountDeleted()); dispatch(accountDeleted());
dispatch(createAlert("Your account has been permanently deleted", "danger")); dispatch(
} catch (err: unknown) { createAlert("Your account has been permanently deleted", "danger"),
errorHandle(dispatch, err) );
} catch (err: unknown) {
errorHandle(dispatch, err);
}
} }
} };
};
+66 -38
View File
@@ -1,17 +1,30 @@
import React, { useState } from "react"; import * as React from 'react';
import { Link, Navigate } from "react-router-dom"; import { Link, Navigate } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { login } from "../../actions/auth"; import { login } from "../../actions/auth";
const Login = () => { import Avatar from '@mui/material/Avatar';
const [email, setEmail] = useState('') import Button from '@mui/material/Button';
const [password, setPassword] = useState('') import CssBaseline from '@mui/material/CssBaseline';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
export default function Login() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated); const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
const onSubmit = async (e: React.SyntheticEvent) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); event.preventDefault();
await dispatch(login(email, password)); const data = new FormData(event.currentTarget);
const email = data.get('email') as string
const password = data.get('pasword') as string
if (email && password)
dispatch(login(email, password));
}; };
if (isAuthenticated) { if (isAuthenticated) {
@@ -19,39 +32,54 @@ const Login = () => {
} }
return ( return (
<section className="container"> <Container component="main" maxWidth="xs">
<h1 className="large text-primary">Sign In</h1> <CssBaseline />
<p className="lead"> <Box
<i className="fas fa-user" /> Sign Into Your Account sx={{
</p> marginTop: 8,
<form className="form" onSubmit={onSubmit}> display: 'flex',
<div className="form-group"> flexDirection: 'column',
<input alignItems: 'center',
type="email" }}
placeholder="Email Address" >
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Log In
</Typography>
<Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email" name="email"
value={email} autoComplete="email"
onChange={(e) => setEmail(e.target.value)} autoFocus
/> />
</div> <TextField
<div className="form-group"> margin="normal"
<input required
type="password" fullWidth
placeholder="Password"
name="password" name="password"
value={password} label="Password"
onChange={(e)=>setPassword(e.target.value)} type="password"
//used to be "6" id="password"
minLength={6} autoComplete="current-password"
/> />
</div> <Button
<input type="submit" className="btn btn-primary" value="Login" /> type="submit"
</form> fullWidth
<p className="my-1"> variant="contained"
Don't have an account? <Link to="/register">Sign Up</Link> sx={{ mt: 3, mb: 2 }}
</p> >
</section> LOG IN
</Button>
</Box>
</Box>
<p> Don't have an account? <Link to="/register">Sign Up</Link></p>
</Container>
); );
}; }
export default Login;
+105 -70
View File
@@ -1,24 +1,36 @@
import React, { useState } from 'react'; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { useAppDispatch, useAppSelector } from '../../utils/hooks'; import { Navigate, Link } from "react-router-dom";
import { Link, Navigate } from 'react-router-dom'; import { createAlert } from "../../actions/alert";
import { createAlert } from '../../actions/alert'; import { register } from "../../actions/auth";
import { register } from '../../actions/auth';
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import TextField from '@mui/material/TextField';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
const Register = () => { export default function SignUp() {
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [password2, setPassword2] = useState('')
const isAuthenticated = useAppSelector(state => state.auth.isAuthenticated) const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const onSubmit = async (e: React.SyntheticEvent) => { const data = new FormData(event.currentTarget);
e.preventDefault(); const password = data.get('password') as string
const password2 = data.get('password') as string
const email = data.get('email') as string
const name = data.get('name') as string
if (password !== password2) { if (password !== password2) {
dispatch(createAlert('Passwords do not match', 'danger')); dispatch(createAlert("Passwords do not match", "danger"));
} else { } else {
console.log(name,email, password)
dispatch(register({ name, email, password })); dispatch(register({ name, email, password }));
} }
}; };
@@ -28,59 +40,82 @@ const Register = () => {
} }
return ( return (
<section className="container"> <Container component="main" maxWidth="xs">
<h1 className="large text-primary">Sign Up</h1> <CssBaseline />
<p className="lead"> <Box
<i className="fas fa-user" /> Create Your Account sx={{
</p> marginTop: 8,
<form className="form" onSubmit={onSubmit}> display: 'flex',
<div className="form-group"> flexDirection: 'column',
<input alignItems: 'center',
type="text" }}
placeholder="Name" >
name="name" <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
value={name} <LockOutlinedIcon />
onChange={(e) => setName(e.target.value)} </Avatar>
/> <Typography component="h1" variant="h5">
</div> Sign up
<div className="form-group"> </Typography>
<input <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
type="email" <Grid container spacing={2}>
placeholder="Email Address" <Grid item xs={12}>
name="email" <TextField
value={email} name="name"
onChange={(e) => setEmail(e.target.value)} required
/> fullWidth
<small className="form-text"> id="name"
This site uses Gravatar so if you want a profile image, use a label="Name"
Gravatar email autoFocus
</small> />
</div> </Grid>
<div className="form-group"> <Grid item xs={12}>
<input <TextField
type="password" required
placeholder="Password" fullWidth
name="password" id="email"
value={password} label="Email Address"
onChange={(e) => setPassword(e.target.value)} name="email"
/> />
</div> </Grid>
<div className="form-group"> <Grid item xs={12}>
<input <TextField
type="password" required
placeholder="Confirm Password" fullWidth
name="password2" name="password"
value={password2} label="Password"
onChange={(e) => setPassword2(e.target.value)} type="password"
/> id="password"
</div> />
<input type="submit" className="btn btn-primary" value="Register" /> </Grid>
</form> <Grid item xs={12}>
<p className="my-1"> <TextField
Already have an account? <Link to="/login">Sign In</Link> required
</p> fullWidth
</section> name="password2"
label="Repeat Password"
type="password"
id="password2"
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Sign Up
</Button>
<Grid container justifyContent="flex-end">
<Grid item>
Already have an account?{' '}
<Link to={'/login'} >
Sign in
</Link>
</Grid>
</Grid>
</Box>
</Box>
</Container>
); );
}; }
export default Register;
+8 -10
View File
@@ -1,10 +1,11 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAppDispatch, useAppSelector } from '../../utils/hooks'; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import DashboardActions from "./DashboardActions"; import DashboardActions from "./DashboardActions";
import Experience from "./Experience"; import Experience from "./Experience";
import Education from "./Education"; import Education from "./Education";
import { getCurrentProfile, deleteAccount } from "../../actions/profile"; import { getCurrentProfile, deleteAccount } from "../../actions/profile";
import { Box, Typography } from "@mui/material";
const Dashboard = () => { const Dashboard = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -17,11 +18,9 @@ const Dashboard = () => {
const user = useAppSelector((state) => state.auth.user); const user = useAppSelector((state) => state.auth.user);
const profile = useAppSelector((state) => state.profile.profile); const profile = useAppSelector((state) => state.profile.profile);
return ( return (
<section className="container"> <Box component='main' justifyContent='center' flexDirection='column' minHeight='50vh' display='flex' alignItems='center'>
<h1 className="large text-primary">Dashboard</h1> <Typography variant="h3" component='h2'>Dashboard </Typography>
<p className="lead"> <Typography variant="h5" component='h3' display='block'> Welcome {user && user.name}</Typography>
<i className="fas fa-user" /> Welcome {user && user.name}
</p>
{profile !== null ? ( {profile !== null ? (
<> <>
<DashboardActions /> <DashboardActions />
@@ -31,7 +30,6 @@ const Dashboard = () => {
<div className="my-2"> <div className="my-2">
<button <button
className="btn btn-danger" className="btn btn-danger"
onClick={async () => await dispatch(deleteAccount())} onClick={async () => await dispatch(deleteAccount())}
> >
<i className="fas fa-user" /> Delete My Account <i className="fas fa-user" /> Delete My Account
@@ -40,13 +38,13 @@ const Dashboard = () => {
</> </>
) : ( ) : (
<> <>
<p>You have not yet setup a profile, please add some info</p> <Typography>You have not yet setup a profile, please add some info</Typography>
<Link to="/create-profile" className="btn btn-primary my-1"> <Link to="/create-profile">
Create Profile Create Profile
</Link> </Link>
</> </>
)} )}
</section> </Box>
); );
}; };
@@ -1,17 +1,17 @@
import React from 'react'; import React from "react";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
const DashboardActions = () => { const DashboardActions = () => {
return ( return (
<div className='dash-buttons'> <div className="dash-buttons">
<Link to='/edit-profile' className='btn btn-light'> <Link to="/edit-profile" className="btn btn-light">
<i className='fas fa-user-circle text-primary' /> Edit Profile <i className="fas fa-user-circle text-primary" /> Edit Profile
</Link> </Link>
<Link to='/add-experience' className='btn btn-light'> <Link to="/add-experience" className="btn btn-light">
<i className='fab fa-black-tie text-primary' /> Add Experience <i className="fab fa-black-tie text-primary" /> Add Experience
</Link> </Link>
<Link to='/add-education' className='btn btn-light'> <Link to="/add-education" className="btn btn-light">
<i className='fas fa-graduation-cap text-primary' /> Add Education <i className="fas fa-graduation-cap text-primary" /> Add Education
</Link> </Link>
</div> </div>
); );
@@ -1,17 +1,17 @@
import React, { Fragment } from 'react'; import React, { Fragment } from "react";
import { deleteExperience } from '../../actions/profile'; import { deleteExperience } from "../../actions/profile";
import { ExperienceType } from '../../types'; import { ExperienceType } from "../../types";
import formatDate from '../../utils/formatDate'; import formatDate from "../../utils/formatDate";
import { useAppDispatch } from '../../utils/hooks'; import { useAppDispatch } from "../../utils/hooks";
const Experience = ({experience}:{experience:ExperienceType[]}) => { const Experience = ({ experience }: { experience: ExperienceType[] }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const experiences = experience.map((exp) => ( const experiences = experience.map((exp) => (
<tr key={exp._id}> <tr key={exp._id}>
<td>{exp.company}</td> <td>{exp.company}</td>
<td className="hide-sm">{exp.title}</td> <td className="hide-sm">{exp.title}</td>
<td> <td>
{formatDate(exp.from)} - {exp.to ? formatDate(exp.to) : 'Now'} {formatDate(exp.from)} - {exp.to ? formatDate(exp.to) : "Now"}
</td> </td>
<td> <td>
<button <button
@@ -42,6 +42,4 @@ const Experience = ({experience}:{experience:ExperienceType[]}) => {
); );
}; };
export default Experience; export default Experience;
+180 -42
View File
@@ -1,56 +1,194 @@
import React, { Fragment } from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { logOut } from "../../reducers/auth"; import { logOut } from "../../reducers/auth";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import Container from "@mui/material/Container";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
import MenuIcon from "@mui/icons-material/Menu";
import AdbIcon from "@mui/icons-material/Adb";
const linkStyle = { color: 'inherit', textDecoration: 'none' }
const Navbar = () => { const Navbar = () => {
const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated); const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
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">Profile</span>
</Link>
</li>
<li>
<a onClick={()=>dispatch(logOut())} href="#!">
<i className="fas fa-sign-out-alt" />{' '}
<span className="hide-sm">Logout</span>
</a>
</li>
</ul>
);
const guestLinks = ( const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
<ul> null,
<li>
<Link to="/profiles">Developers</Link>
</li>
<li>
<Link to="/register">Register</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
</ul>
); );
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const authLinksSmall = [
(<MenuItem onClick={handleCloseNavMenu} key={1}>
<Link style={linkStyle} to={'/profiles'}>Developers</Link>
</MenuItem>),
(<MenuItem onClick={handleCloseNavMenu} key={2}>
<Link style={linkStyle} to={'/posts'}>Posts</Link>
</MenuItem>),
(<MenuItem onClick={handleCloseNavMenu} key={3}>
<Link style={linkStyle} to={'/profile'}>Profile</Link>
</MenuItem>),
(<MenuItem onClick={handleCloseNavMenu} key={4}>
<a style={linkStyle} onClick={() => dispatch(logOut())} href="#!">
<span>Logout</span>
</a>
</MenuItem>)]
const guestLinksSmall = [
(
<MenuItem onClick={handleCloseNavMenu} key={1}>
<Link style={linkStyle} to={'/profiles'}>Developers</Link>
</MenuItem>),
(<MenuItem onClick={handleCloseNavMenu} key={2}>
<Link style={linkStyle} to={'/register'}>Register</Link>
</MenuItem>),
(<MenuItem onClick={handleCloseNavMenu} key={3}>
<Link style={linkStyle} to={'/login'}>Login</Link>
</MenuItem>)]
const authLinksBig = [
(<Button key={1}
sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/profiles'} >Developers</Link>
</Button>),
(<Button key={2}
sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/posts'}>posts</Link>
</Button>),
(<Button
key={3} sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/profile'}>Profile</Link>
</Button>
), (<Button
key={4} sx={{ my: 2, color: "white", display: "block" }}
>
<a style={linkStyle} onClick={() => dispatch(logOut())} href="#!">
<span>Logout</span>
</a>
</Button>
)]
const guestLinksBig = [
(<Button key={1}
sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/profiles'} >Developers</Link>
</Button>),
(<Button key={2}
sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/register'}>Register</Link>
</Button>),
(<Button key={3}
sx={{ my: 2, color: "white", display: "block" }}
>
<Link style={linkStyle} to={'/login'}>Login</Link>
</Button>)
];
return ( return (
<nav className="navbar bg-dark"> <AppBar position="static">
<h1> <Container maxWidth="xl">
<Link to="/posts"> <Toolbar disableGutters>
<i className="fas fa-code" /> DevConnector {/*Icon for big dislay*/}
</Link> <AdbIcon sx={{ display: { xs: "none", md: "flex" }, mr: 1 }} />
</h1> {/*LOGO link for bi display*/}
<Fragment>{isAuthenticated ? authLinks : guestLinks}</Fragment> <Typography
</nav> variant="h6"
noWrap
component="h6"
sx={{
mr: 2,
display: { xs: "none", md: "flex" },
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: ".3rem",
color: "inherit",
textDecoration: "none",
}}
>
<Link style={linkStyle} to={"/posts"}>DevConnect</Link>
</Typography>
{/* Small display menu*/}
<Box
sx={{
flexGrow: 1,
display: { xs: "flex", md: "none" },
}}
>
{/* Small display button */}
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
{/* The actual menu that pops when you click the button */}
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: "block", md: "none" },
}}
>
{isAuthenticated ? authLinksSmall.map(el => el) : guestLinksSmall.map(el => el)}
</Menu>
</Box>
{/* Icon for small display */}
<AdbIcon sx={{ display: { xs: "flex", md: "none" }, mr: 1 }} />
{/* LOGO text link small display*/}
<Typography
variant="h5"
noWrap
component="h5"
sx={{
mr: 2,
display: { xs: "flex", md: "none" },
flexGrow: 1,
fontFamily: "monospace",
letterSpacing: ".3rem",
color: "inherit",
textDecoration: "none",
}}
>
<Link style={linkStyle} to={"/posts"}>DevConnect</Link>
</Typography>
{/* Menu bar for big display*/}
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
{isAuthenticated ? authLinksBig.map(el => el) : guestLinksBig.map(el => el)}
</Box>
</Toolbar>
</Container>
</AppBar>
); );
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import React from 'react'; import React from "react";
const NotFound = () => { const NotFound = () => {
return ( return (
+3 -3
View File
@@ -1,11 +1,11 @@
import React, { Fragment } from 'react'; import React, { Fragment } from "react";
import spinner from './spinner.gif'; import spinner from "./spinner.gif";
const Spinner = () => ( const Spinner = () => (
<Fragment> <Fragment>
<img <img
src={spinner} src={spinner}
style={{ width: '200px', margin: 'auto', display: 'block' }} style={{ width: "200px", margin: "auto", display: "block" }}
alt="Loading..." alt="Loading..."
/> />
</Fragment> </Fragment>
+13 -15
View File
@@ -1,39 +1,37 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { addComment } from '../../actions/post'; import { addComment } from "../../actions/post";
import { useAppDispatch } from '../../utils/hooks'; import { useAppDispatch } from "../../utils/hooks";
const CommentForm = ({ postId }: { postId: string }) => { const CommentForm = ({ postId }: { postId: string }) => {
const [text, setText] = useState(''); const [text, setText] = useState("");
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
return ( return (
<div className='post-form'> <div className="post-form">
<div className='bg-primary p'> <div className="bg-primary p">
<h3>Leave a Comment</h3> <h3>Leave a Comment</h3>
</div> </div>
<form <form
className='form my-1' className="form my-1"
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
await dispatch(addComment(postId, { text })); await dispatch(addComment(postId, { text }));
setText(''); setText("");
}} }}
> >
<textarea <textarea
name='text' name="text"
cols={30} cols={30}
rows={5} rows={5}
placeholder='Comment the post' placeholder="Comment the post"
value={text} value={text}
onChange={e => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
required required
/> />
<input type='submit' className='btn btn-dark my-1' value='Submit' /> <input type="submit" className="btn btn-dark my-1" value="Submit" />
</form> </form>
</div> </div>
); );
}; };
export default CommentForm;
export default CommentForm
+2 -2
View File
@@ -5,8 +5,8 @@ import { deleteComment } from "../../actions/post";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { Comment } from "../../types"; import { Comment } from "../../types";
interface CommentItemProps { interface CommentItemProps {
postId: string, postId: string;
comment: Comment comment: Comment;
} }
const CommentItem = ({ const CommentItem = ({
postId, postId,
+3 -1
View File
@@ -7,7 +7,9 @@ import { useAppDispatch, useAppSelector } from "../../utils/hooks";
const PostItem = ({ const PostItem = ({
post: { _id, text, name, avatar, user, likes, comments, date }, post: { _id, text, name, avatar, user, likes, comments, date },
}: { post: Post }) => { }: {
post: Post;
}) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const auth = useAppSelector((state) => state.auth); const auth = useAppSelector((state) => state.auth);
return ( return (
-1
View File
@@ -13,7 +13,6 @@ const Posts = () => {
fetchData(); fetchData();
}, [dispatch]); }, [dispatch]);
const posts = useAppSelector((state) => state.post.posts); const posts = useAppSelector((state) => state.post.posts);
return ( return (
<section className="container"> <section className="container">
@@ -19,8 +19,9 @@ const AddEducation = () => {
const { school, degree, fieldofstudy, from, to, description, current } = const { school, degree, fieldofstudy, from, to, description, current } =
formData; formData;
const onChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => const onChange = (
setFormData({ ...formData, [event.target.name]: event.target.value }); event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
) => setFormData({ ...formData, [event.target.name]: event.target.value });
return ( return (
<section className="container"> <section className="container">
@@ -34,7 +35,9 @@ const AddEducation = () => {
className="form" className="form"
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
await dispatch(addEducation(formData)).then(() => navigate("/dashboard")); await dispatch(addEducation(formData)).then(() =>
navigate("/dashboard"),
);
// i have no idea how this works used to work, i removed the navigate function from the addEducation and it does now // i have no idea how this works used to work, i removed the navigate function from the addEducation and it does now
}} }}
> >
@@ -113,5 +116,4 @@ const AddEducation = () => {
); );
}; };
export default AddEducation; export default AddEducation;
@@ -1,25 +1,26 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from "react-router-dom";
import { addExperience } from '../../actions/profile'; import { addExperience } from "../../actions/profile";
import { useAppDispatch } from '../../utils/hooks'; import { useAppDispatch } from "../../utils/hooks";
const AddExperience = () => { const AddExperience = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
company: '', company: "",
title: '', title: "",
location: '', location: "",
from: '', from: "",
to: '', to: "",
current: false, current: false,
description: '' description: "",
}); });
const { company, title, location, from, to, current, description } = formData; const { company, title, location, from, to, current, description } = formData;
const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => const onChange = (
setFormData({ ...formData, [e.target.name]: e.target.value }); e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => setFormData({ ...formData, [e.target.name]: e.target.value });
return ( return (
<section className="container"> <section className="container">
@@ -33,7 +34,9 @@ const AddExperience = () => {
className="form" className="form"
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
await dispatch(addExperience(formData)).then(() => navigate('/dashboard')); await dispatch(addExperience(formData)).then(() =>
navigate("/dashboard"),
);
}} }}
> >
<div className="form-group"> <div className="form-group">
@@ -79,7 +82,7 @@ const AddExperience = () => {
onChange={() => { onChange={() => {
setFormData({ ...formData, current: !current }); setFormData({ ...formData, current: !current });
}} }}
/>{' '} />{" "}
Current Job Current Job
</p> </p>
</div> </div>
@@ -1,3 +1,4 @@
import { Button, Box, Container, CssBaseline, Grid, InputLabel, MenuItem, Select, TextField, Typography } from "@mui/material";
import React, { Fragment, useState, useEffect } from "react"; import React, { Fragment, useState, useEffect } from "react";
import { Link, useMatch, useNavigate } from "react-router-dom"; import { Link, useMatch, useNavigate } from "react-router-dom";
import { createProfile, getCurrentProfile } from "../../actions/profile"; import { createProfile, getCurrentProfile } from "../../actions/profile";
@@ -19,13 +20,10 @@ const initialState = {
}; };
const ProfileForm = () => { const ProfileForm = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { profile, loading }: { profile: any, loading: boolean } = useAppSelector((state) => state.profile); const { profile, loading }: { profile: any; loading: boolean } =
//TODO useAppSelector((state) => state.profile);
// issue with for in
const [formData, setFormData] = useState(initialState); const [formData, setFormData] = useState(initialState);
const creatingProfile = useMatch("/create-profile"); const creatingProfile = useMatch("/create-profile");
@@ -37,7 +35,7 @@ const ProfileForm = () => {
useEffect(() => { useEffect(() => {
// if there is no profile, attempt to fetch one // if there is no profile, attempt to fetch one
async function fetchData() { async function fetchData() {
await dispatch(getCurrentProfile()) await dispatch(getCurrentProfile());
} }
if (!profile) fetchData(); if (!profile) fetchData();
@@ -60,208 +58,222 @@ const ProfileForm = () => {
} }
}, [loading, dispatch, profile]); }, [loading, dispatch, profile]);
const {
company,
website,
location,
status,
skills,
githubusername,
bio,
twitter,
facebook,
linkedin,
youtube,
instagram,
} = formData;
const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async (e: React.SyntheticEvent) => {
const editing = profile ? true : false; const editing = profile ? true : false;
e.preventDefault(); e.preventDefault();
await dispatch(createProfile(formData, editing)).then(() => {
const data = new FormData(e.currentTarget);
const website = data.get('website') as string
const location = data.get('location') as string
const skills = data.get('skills') as string
const githubusername = data.get('githubUser') as string
const company = data.get('company') as string
const bio = data.get('bio') as string
const twitter = data.get('twitter') as string
const facebook = data.get('facebook') as string
const youtube = data.get('youtube') as string
const linkedin = data.get('linkedin') as string
const instagram = data.get('instagram') as string
const status =data.get('status') as string
console.log(data)
await dispatch(createProfile({
website,
location,
skills,
githubusername,
company,
bio,
twitter,
facebook,
youtube,
linkedin,
instagram,
status
}, editing)).then(() => {
if (!editing) navigate("/dashboard"); if (!editing) navigate("/dashboard");
}); });
}; };
return ( return (
<section className="container"> <Container className="container">
<h1 className="large text-primary"> <CssBaseline />
<Typography component='h1' variant="h4">
{creatingProfile ? "Create Your Profile" : "Edit Your Profile"} {creatingProfile ? "Create Your Profile" : "Edit Your Profile"}
</h1> </Typography>
<p className="lead"> <Typography component='p' variant='body1'>
<i className="fas fa-user" />
{creatingProfile {creatingProfile
? ` Let's get some information to make your` ? ` Let's get some information to make your`
: " Add some changes to your profile"} : " Add some changes to your profile"}
</p> </Typography>
<small>* = required field</small> <Typography variant="body2">* = required field</Typography>
<form className="form" onSubmit={onSubmit}> <Box
<div className="form-group"> component="form"
<select name="status" value={status} onChange={onChange}> noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
<option>* Select Professional Status</option> <Grid container spacing={2}>
<option value="Developer">Developer</option> <Grid item xs={6}>
<option value="Junior Developer">Junior Developer</option> <TextField
<option value="Senior Developer">Senior Developer</option> name="company"
<option value="Manager">Manager</option> fullWidth
<option value="Student or Learning">Student or Learning</option> label="Company"
<option value="Instructor">Instructor or Teacher</option> autoFocus
<option value="Intern">Intern</option> />
<option value="Other">Other</option> </Grid>
</select> <Grid item xs={6}>
<small className="form-text"> <Typography paddingY='1rem'>
Give us an idea of where you are at in your career Could be your own company or onee you work for
</small> </Typography>
</div> </Grid>
<div className="form-group"> <Grid item xs={6}>
<input <TextField
type="text" name="website"
placeholder="Company" fullWidth
name="company" label="Website"
value={company} />
onChange={onChange} </Grid>
/> <Grid item xs={6}>
<small className="form-text"> <Typography paddingY='1rem'>
Could be your own company or one you work for Could be your own a or a company website
</small> </Typography>
</div> </Grid>
<div className="form-group"> <Grid item xs={6}>
<input <TextField
type="text" name="location"
placeholder="Website" fullWidth
name="website" label="Location"
value={website} />
onChange={onChange} </Grid>
/> <Grid item xs={6}>
<small className="form-text"> <Typography paddingY='1rem'>
Could be your own or a company website City & state suggest(eg. Boston MA)
</small> </Typography>
</div> </Grid>
<div className="form-group"> <Grid item xs={6}>
<input <TextField
type="text" name="skills"
placeholder="Location" required
name="location" fullWidth
value={location} label="Skills"
onChange={onChange} />
/> </Grid>
<small className="form-text"> <Grid item xs={6}>
City & state suggested (eg. Boston, MA) <Typography>
</small> Please use comma separeted values(eg. HTML, CSS, JavaScript, PHP)
</div> </Typography>
<div className="form-group"> </Grid>
<input <Grid item xs={6}>
type="text" <TextField
placeholder="* Skills" name="githubUser"
name="skills" fullWidth
value={skills} label="Github Username"
onChange={onChange} />
/> </Grid>
<small className="form-text"> <Grid item xs={6}>
Please use comma separated values (eg. HTML,CSS,JavaScript,PHP) <Typography>
</small> If you want your latest reepos add a Github link, include your username
</div> </Typography>
<div className="form-group"> </Grid>
<input <Grid item xs={6}>
type="text" <InputLabel id='status'>Status *</InputLabel>
placeholder="Github Username" <Select fullWidth
name="githubusername" labelId="status"
value={githubusername} name='status'
onChange={onChange} required
/> placeholder='Select Profesional status'
<small className="form-text"> defaultValue={""}
If you want your latest repos and a Github link, include your >
username <MenuItem value="">None</MenuItem>
</small> <MenuItem value="Developer">Developer</MenuItem>
</div> <MenuItem value="Junior Developer">Junior Developer</MenuItem>
<div className="form-group"> <MenuItem value="Senior Developer">Senior Developer</MenuItem>
<textarea <MenuItem value="Manager">Manager</MenuItem>
placeholder="A short bio of yourself" <MenuItem value="Student or Learning">Student or Learning</MenuItem>
name="bio" <MenuItem value="Instructor">Instructor or Teacher</MenuItem>
value={bio} <MenuItem value="Intern">Intern</MenuItem>
onChange={onChange} <MenuItem value="Other">Other</MenuItem>
/> </Select>
<small className="form-text">Tell us a little about yourself</small> </Grid>
</div> <Grid item xs={6}>
<Typography paddingTop='2.3rem' >Select Profesional Status</Typography>
<div className="my-2"> </Grid>
<button <Grid item xs={12}>
onClick={() => toggleSocialInputs(!displaySocialInputs)} <Typography>
type="button" Tell us a little about yourself
className="btn btn-light" </Typography>
> </Grid>
Add Social Network Links <Grid item xs={12} >
</button> <TextField
<span>Optional</span> name="bio"
</div> multiline
rows={4}
{displaySocialInputs && ( fullWidth
<Fragment> label="A short bio of yourself"
<div className="form-group social-input"> />
<i className="fab fa-twitter fa-2x" /> </Grid>
<input <Grid item xs={12}>
type="text" <Button
placeholder="Twitter URL" variant='contained'
name="twitter" onClick={() => toggleSocialInputs(!displaySocialInputs)}>
value={twitter} Add Social Network Links
onChange={onChange} </Button>
/> </Grid>
</div> {displaySocialInputs ? (
<>
<div className="form-group social-input"> <Grid item xs={6}>
<i className="fab fa-facebook fa-2x" /> <TextField
<input fullWidth
type="text" name="twitter"
placeholder="Facebook URL" label="Twitter URL"
name="facebook" />
value={facebook} </Grid>
onChange={onChange} <Grid item xs={6}>
/> </Grid>
</div> <Grid item xs={6}>
<TextField
<div className="form-group social-input"> fullWidth
<i className="fab fa-youtube fa-2x" /> name="facebook"
<input label="FaceBook URL"
type="text" />
placeholder="YouTube URL" </Grid>
name="youtube" <Grid item xs={6}>
value={youtube} </Grid>
onChange={onChange} <Grid item xs={6}>
/> <TextField
</div> fullWidth
name="youtube"
<div className="form-group social-input"> label="YouTube URL"
<i className="fab fa-linkedin fa-2x" /> />
<input </Grid>
type="text" <Grid item xs={6}>
placeholder="Linkedin URL" </Grid>
name="linkedin" <Grid item xs={6}>
value={linkedin} <TextField
onChange={onChange} fullWidth
/> name="linkedin"
</div> label="Linkedin URL"
/>
<div className="form-group social-input"> </Grid>
<i className="fab fa-instagram fa-2x" /> <Grid item xs={6}>
<input </Grid>
type="text" <Grid item xs={6}>
placeholder="Instagram URL" <TextField
name="instagram" fullWidth
value={instagram} name="instagram"
onChange={onChange} label="Instagram URL"
/> />
</div> </Grid>
</Fragment> </>) : null}
)} <Grid item xs={8}>
<Button variant='contained' type="submit">Sumbit query</Button>
<input type="submit" className="btn btn-primary my-1" /> </Grid>
<Link className="btn btn-light my-1" to="/dashboard"> <Grid item xs={2}>
Go Back <Button variant='outlined' >
</Link> <Link style={{ color: 'inherit', textDecoration: 'none' }} to='/dashboard'>Go Back</Link>
</form> </Button>
</section> </Grid>
</Grid>
</Box>
</Container>
); );
}; };
+3 -4
View File
@@ -18,8 +18,7 @@ const Profile = () => {
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
if (typeof id === 'string') if (typeof id === "string") await dispatch(getProfileById(id));
await dispatch(getProfileById(id));
} }
fetchData(); fetchData();
}, [dispatch, id]); }, [dispatch, id]);
@@ -48,7 +47,7 @@ const Profile = () => {
<h2 className="text-primary">Experience</h2> <h2 className="text-primary">Experience</h2>
{profile.experience.length > 0 ? ( {profile.experience.length > 0 ? (
<Fragment> <Fragment>
{profile.experience.map((experience:ExperienceType) => ( {profile.experience.map((experience: ExperienceType) => (
<ProfileExperience <ProfileExperience
key={experience._id} key={experience._id}
experience={experience} experience={experience}
@@ -64,7 +63,7 @@ const Profile = () => {
<h2 className="text-primary">Education</h2> <h2 className="text-primary">Education</h2>
{profile.education.length > 0 ? ( {profile.education.length > 0 ? (
<Fragment> <Fragment>
{profile.education.map((education:EducationType) => ( {profile.education.map((education: EducationType) => (
<ProfileEducation <ProfileEducation
key={education._id} key={education._id}
education={education} education={education}
+14 -13
View File
@@ -1,31 +1,32 @@
import React, { Fragment } from 'react'; import React, { Fragment } from "react";
import { ProfileType } from '../../types'; import { ProfileType } from "../../types";
const ProfileAbout = ({ const ProfileAbout = ({
profile: { profile: {
bio, bio,
skills, skills,
user: { name } user: { name },
} },
}:{profile:ProfileType}) => ( }: {
<div className='profile-about bg-light p-2'> profile: ProfileType;
}) => (
<div className="profile-about bg-light p-2">
{bio && ( {bio && (
<Fragment> <Fragment>
<h2 className='text-primary'>{name.trim().split(' ')[0]}s Bio</h2> <h2 className="text-primary">{name.trim().split(" ")[0]}s Bio</h2>
<p>{bio}</p> <p>{bio}</p>
<div className='line' /> <div className="line" />
</Fragment> </Fragment>
)} )}
<h2 className='text-primary'>Skill Set</h2> <h2 className="text-primary">Skill Set</h2>
<div className='skills'> <div className="skills">
{skills.map((skill, index) => ( {skills.map((skill, index) => (
<div key={index} className='p-1'> <div key={index} className="p-1">
<i className='fas fa-check' /> {skill} <i className="fas fa-check" /> {skill}
</div> </div>
))} ))}
</div> </div>
</div> </div>
); );
export default ProfileAbout; export default ProfileAbout;
@@ -1,14 +1,16 @@
import React from 'react'; import React from "react";
import { EducationType } from '../../types'; import { EducationType } from "../../types";
import formatDate from '../../utils/formatDate'; import formatDate from "../../utils/formatDate";
const ProfileEducation = ({ const ProfileEducation = ({
education: { school, degree, fieldofstudy, current, to, from, description } education: { school, degree, fieldofstudy, current, to, from, description },
}: { education: EducationType }) => ( }: {
education: EducationType;
}) => (
<div> <div>
<h3 className="text-dark">{school}</h3> <h3 className="text-dark">{school}</h3>
<p> <p>
{formatDate(from)} - {to ? formatDate(to) : 'Now'} {formatDate(from)} - {to ? formatDate(to) : "Now"}
</p> </p>
<p> <p>
<strong>Degree: </strong> {degree} <strong>Degree: </strong> {degree}
@@ -22,6 +24,4 @@ const ProfileEducation = ({
</div> </div>
); );
export default ProfileEducation; export default ProfileEducation;
@@ -1,14 +1,16 @@
import React from 'react'; import React from "react";
import { ExperienceType } from '../../types'; import { ExperienceType } from "../../types";
import formatDate from '../../utils/formatDate'; import formatDate from "../../utils/formatDate";
const ProfileExperience = ({ const ProfileExperience = ({
experience: { company, title, location, current, to, from, description } experience: { company, title, location, current, to, from, description },
}: { experience: ExperienceType }) => ( }: {
experience: ExperienceType;
}) => (
<div> <div>
<h3 className="text-dark">{company}</h3> <h3 className="text-dark">{company}</h3>
<p> <p>
{formatDate(from)} - {to ? formatDate(to) : 'Now'} {formatDate(from)} - {to ? formatDate(to) : "Now"}
</p> </p>
<p> <p>
<strong>Position: </strong> {title} <strong>Position: </strong> {title}
@@ -22,5 +24,4 @@ const ProfileExperience = ({
</div> </div>
); );
export default ProfileExperience; export default ProfileExperience;
+14 -12
View File
@@ -10,7 +10,9 @@ const ProfileTop = ({
social, social,
user: { name, avatar }, user: { name, avatar },
}, },
}: { profile: ProfileType }) => { }: {
profile: ProfileType;
}) => {
return ( return (
<div className="profile-top bg-primary p-2"> <div className="profile-top bg-primary p-2">
<img className="round-img my-1" src={avatar} alt="" /> <img className="round-img my-1" src={avatar} alt="" />
@@ -27,17 +29,17 @@ const ProfileTop = ({
) : null} ) : null}
{social {social
? Object.entries(social) ? Object.entries(social)
.filter(([_, value]) => value) .filter(([_, value]) => value)
.map(([key, value]) => ( .map(([key, value]) => (
<a <a
key={key} key={key}
href={value} href={value}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i className={`fab fa-${key} fa-2x`}></i> <i className={`fab fa-${key} fa-2x`}></i>
</a> </a>
)) ))
: null} : null}
</div> </div>
</div> </div>
+14 -13
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from "react";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import { ProfileType } from '../../types'; import { ProfileType } from "../../types";
const ProfileItem = ({ const ProfileItem = ({
profile: { profile: {
@@ -8,26 +8,28 @@ const ProfileItem = ({
status, status,
company, company,
location, location,
skills skills,
} },
}: { profile: ProfileType }) => { }: {
profile: ProfileType;
}) => {
return ( return (
<div className='profile bg-light'> <div className="profile bg-light">
<img src={avatar} alt='' className='round-img' /> <img src={avatar} alt="" className="round-img" />
<div> <div>
<h2>{name}</h2> <h2>{name}</h2>
<p> <p>
{status} {company && <span> at {company}</span>} {status} {company && <span> at {company}</span>}
</p> </p>
<p className='my-1'>{location && <span>{location}</span>}</p> <p className="my-1">{location && <span>{location}</span>}</p>
<Link to={`/profile/${_id}`} className='btn btn-primary'> <Link to={`/profile/${_id}`} className="btn btn-primary">
View Profile View Profile
</Link> </Link>
</div> </div>
<ul> <ul>
{skills.slice(0, 4).map((skill, index) => ( {skills.slice(0, 4).map((skill, index) => (
<li key={index} className='text-primary'> <li key={index} className="text-primary">
<i className='fas fa-check' /> {skill} <i className="fas fa-check" /> {skill}
</li> </li>
))} ))}
</ul> </ul>
@@ -36,4 +38,3 @@ const ProfileItem = ({
}; };
export default ProfileItem; export default ProfileItem;
+2 -2
View File
@@ -6,14 +6,14 @@ import { useAppDispatch, useAppSelector } from "../../utils/hooks";
const Profiles = () => { const Profiles = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
await dispatch(getProfiles()); await dispatch(getProfiles());
} }
fetchData(); fetchData();
}, [dispatch]); }, [dispatch]);
const { profiles, loading } = useAppSelector((state) => state.profile); const { profiles, loading } = useAppSelector((state) => state.profile);
return ( return (
<section className="container"> <section className="container">
@@ -3,7 +3,11 @@ import { Navigate } from "react-router-dom";
import Spinner from "../layout/Spinner"; import Spinner from "../layout/Spinner";
import { useAppSelector } from "../../utils/hooks"; import { useAppSelector } from "../../utils/hooks";
const PrivateRoute = ({ component: Component }: { component: () => JSX.Element }) => { const PrivateRoute = ({
component: Component,
}: {
component: () => JSX.Element;
}) => {
const { isAuthenticated, loading } = useAppSelector((state) => state.auth); const { isAuthenticated, loading } = useAppSelector((state) => state.auth);
if (loading) return <Spinner />; if (loading) return <Spinner />;
if (isAuthenticated) return <Component />; if (isAuthenticated) return <Component />;
+1 -1
View File
@@ -1,5 +1,5 @@
declare module "*.gif" { declare module "*.gif" {
const path: string; const path: string;
export default path export default path;
} }
// bonkers have no idea how this works // bonkers have no idea how this works
+5 -6
View File
@@ -1,13 +1,12 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom/client'; import ReactDOM from "react-dom/client";
import App from './App'; import App from "./App";
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById("root") as HTMLElement,
); );
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode> </React.StrictMode>,
); );
+1 -1
View File
@@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
import type { Alert } from "../types"; import type { Alert } from "../types";
import { PayloadAction } from "@reduxjs/toolkit"; import { PayloadAction } from "@reduxjs/toolkit";
type AlertState = Alert[] type AlertState = Alert[];
const initialState: AlertState = []; const initialState: AlertState = [];
+5 -5
View File
@@ -1,12 +1,12 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
import { PayloadAction } from "@reduxjs/toolkit"; import { PayloadAction } from "@reduxjs/toolkit";
import type { User } from '../types' import type { User } from "../types";
interface authState { interface authState {
token: string | null token: string | null;
isAuthenticated: boolean | null isAuthenticated: boolean | null;
loading: boolean loading: boolean;
user: User | null user: User | null;
} }
const initialState: authState = { const initialState: authState = {
+9 -9
View File
@@ -3,10 +3,10 @@ import { PayloadAction } from "@reduxjs/toolkit";
import type { Post, Comment } from "../types"; import type { Post, Comment } from "../types";
interface postState { interface postState {
posts: Post[] posts: Post[];
post: Post | null post: Post | null;
loading: boolean loading: boolean;
error: {} error: {};
//Todo erros //Todo erros
} }
const initialState: postState = { const initialState: postState = {
@@ -62,7 +62,7 @@ const postSlice = createSlice({
posts: state.posts.map((post) => posts: state.posts.map((post) =>
post._id === action.payload.id post._id === action.payload.id
? { ...post, likes: action.payload.likes } ? { ...post, likes: action.payload.likes }
: post : post,
), ),
loading: false, loading: false,
}; };
@@ -74,9 +74,9 @@ const postSlice = createSlice({
...state, ...state,
post: { ...state.post, comments: action.payload }, post: { ...state.post, comments: action.payload },
loading: false, loading: false,
} };
} }
throw new Error('post missing from state') throw new Error("post missing from state");
}, },
removeComment(state, action: PayloadAction<String>) { removeComment(state, action: PayloadAction<String>) {
if ("post" in state && state.post !== null) { if ("post" in state && state.post !== null) {
@@ -85,13 +85,13 @@ const postSlice = createSlice({
post: { post: {
...state.post, ...state.post,
comments: state.post.comments.filter( comments: state.post.comments.filter(
(comment) => comment._id !== action.payload (comment) => comment._id !== action.payload,
), ),
}, },
loading: false, loading: false,
}; };
} }
throw new Error('post missing from state') throw new Error("post missing from state");
}, },
}, },
}); });
+6 -6
View File
@@ -2,12 +2,12 @@ import { createSlice } from "@reduxjs/toolkit";
import { Repo, ProfileType } from "../types"; import { Repo, ProfileType } from "../types";
type ProfileState = { type ProfileState = {
profile: ProfileType | null profile: ProfileType | null;
profiles: ProfileType[] profiles: ProfileType[];
repos: Repo[] repos: Repo[];
loading: boolean loading: boolean;
error: {} error: {};
} };
const initialState: ProfileState = { const initialState: ProfileState = {
profile: null, profile: null,
+11 -13
View File
@@ -1,20 +1,19 @@
import setAuthToken from './utils/setAuthToken'; import setAuthToken from "./utils/setAuthToken";
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from "@reduxjs/toolkit";
import alertReducer from './reducers/alert';
import authReducer from './reducers/auth';
import profileReducer from './reducers/profile'
import postReducer from './reducers/post'
import alertReducer from "./reducers/alert";
import authReducer from "./reducers/auth";
import profileReducer from "./reducers/profile";
import postReducer from "./reducers/post";
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
alert: alertReducer, alert: alertReducer,
auth: authReducer, auth: authReducer,
profile: profileReducer, profile: profileReducer,
post: postReducer post: postReducer,
} },
}) });
let currentState = store.getState(); let currentState = store.getState();
@@ -29,8 +28,7 @@ store.subscribe(() => {
} }
}); });
export type RootState = ReturnType<typeof store.getState> export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch export type AppDispatch = typeof store.dispatch;
export default store; export default store;
+83 -80
View File
@@ -1,93 +1,96 @@
import { AnyAction, ThunkAction } from "@reduxjs/toolkit";
import { RootState } from "./store";
import { AnyAction, ThunkAction } from "@reduxjs/toolkit" export type AppThunk<ReturnType = void> = ThunkAction<
import { RootState } from "./store" ReturnType,
RootState,
unknown,
AnyAction
>;
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, AnyAction> export type FormData = { name: string; email: string; password: string };
export type FormData = { name: string, email: string, password: string }
export type Repo = { export type Repo = {
id: string id: string;
html_url: string html_url: string;
name: string name: string;
description: string description: string;
stargazers_count: number stargazers_count: number;
watchers_count: number watchers_count: number;
forks_count: number forks_count: number;
} };
export type Post = { export type Post = {
user: string, user: string;
text: string text: string;
name: string, name: string;
avatar: string, avatar: string;
likes: User[] likes: User[];
comments: Comment[] comments: Comment[];
date: string date: string;
_id: string _id: string;
} };
export type Comment = { export type Comment = {
user: string user: string;
text: string text: string;
name: string, name: string;
avatar: string, avatar: string;
date: string date: string;
_id: string _id: string;
} };
export type User = { export type User = {
name: string, name: string;
email: string, email: string;
password: string, password: string;
avatar: string, avatar: string;
data: string data: string;
_id: string _id: string;
} };
export type ProfileType = { export type ProfileType = {
user: User user: User;
company: string, company: string;
website: string website: string;
location: string location: string;
status: string status: string;
skills: string[] skills: string[];
bio: string bio: string;
githubusername: string githubusername: string;
experience: ExperienceType[] experience: ExperienceType[];
date: string date: string;
education: EducationType[] education: EducationType[];
social: Social social: Social;
_id: string _id: string;
};
}
export type ExperienceType = { export type ExperienceType = {
title: string title: string;
company: string company: string;
location: string location: string;
from: string from: string;
to: string to: string;
current: boolean current: boolean;
description: string description: string;
_id: string _id: string;
} };
export type EducationType = { export type EducationType = {
school: string school: string;
degree: string degree: string;
fieldofstudy: string fieldofstudy: string;
from: string from: string;
to: string to: string;
current: boolean current: boolean;
description: string description: string;
_id: string _id: string;
} };
export type Social = { export type Social = {
youtube: string youtube: string;
twitter: string twitter: string;
facebook: string facebook: string;
linkedin: string linkedin: string;
instagram: string instagram: string;
_id: string _id: string;
} };
export type Alert = { export type Alert = {
msg: string msg: string;
alertType: string alertType: string;
id: string id: string;
} };
+7 -7
View File
@@ -1,20 +1,20 @@
import axios from 'axios'; import axios from "axios";
import store from '../store'; import store from "../store";
const api = axios.create({ const api = axios.create({
baseURL: '/api', baseURL: "/api",
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}); });
api.interceptors.response.use( api.interceptors.response.use(
(res) => res, (res) => res,
(err) => { (err) => {
if (err.response.status === 401) { if (err.response.status === 401) {
store.dispatch({ type: 'auth/logOut' }); store.dispatch({ type: "auth/logOut" });
} }
return Promise.reject(err); return Promise.reject(err);
} },
); );
export default api; export default api;
+6 -6
View File
@@ -1,8 +1,8 @@
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from 'react-redux' import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from '../store' import type { RootState, AppDispatch } from "../store";
// Use throughout your app instead of plain `useDispatch` and `useSelector` // Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppDispatch2 = () => useDispatch<AppDispatch> export const useAppDispatch2 = () => useDispatch<AppDispatch>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+5 -5
View File
@@ -1,14 +1,14 @@
import api from './api'; import api from "./api";
// store our JWT in LS and set axios headers if we do have a token // store our JWT in LS and set axios headers if we do have a token
const setAuthToken = (token: any) => { const setAuthToken = (token: any) => {
if (token) { if (token) {
api.defaults.headers.common['x-auth-token'] = token; api.defaults.headers.common["x-auth-token"] = token;
localStorage.setItem('token', token); localStorage.setItem("token", token);
} else { } else {
delete api.defaults.headers.common['x-auth-token']; delete api.defaults.headers.common["x-auth-token"];
localStorage.removeItem('token'); localStorage.removeItem("token");
} }
}; };
+2 -8
View File
@@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@@ -20,7 +16,5 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": [ "include": ["src"]
"src"
]
} }