Login Logout Navbar ProfileForm moved to MUI

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