Login Logout Navbar ProfileForm moved to MUI
This commit is contained in:
Generated
+2074
-4050
File diff suppressed because it is too large
Load Diff
@@ -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 |
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
+16
-18
@@ -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
@@ -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(() => {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: darkorange;
|
||||
}
|
||||
body {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
const NotFound = () => {
|
||||
return (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 />;
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
declare module "*.gif" {
|
||||
const path: string;
|
||||
export default path
|
||||
export default path;
|
||||
}
|
||||
// bonkers have no idea how this works
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user