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